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

Compare commits

..

18 Commits

Author SHA1 Message Date
Mark
4094adc5b4 企业微信微信客服字段同步 (#670)
* 企业微信微信客服字段同步

* fix ci lint

---------

Co-authored-by: liuyuezhong <liuyuezhong@inke.cn>
2023-04-18 11:07:06 +08:00
misu
cb0928a03c 获取企业微信实例,设置全局Cache (#671)
Co-authored-by: masong <misu99@github.com>
2023-04-18 11:06:39 +08:00
牛强
d6371c7289 chore: 移除 panic,转移 cache 初始化到 Wechat 结构体方法下;在使用时可以只设置一次 cache 同时避免 panic 出现 (#668) 2023-04-13 14:23:46 +08:00
okhowang
07b7dc40fc cache增加带Context版本,开放平台相关接口支持Context版本 (#653) 2023-04-03 20:32:44 +08:00
Ace
01784c2a4a fix:修复互通红包和视频号消息部分字段错误的数据类型声明 (#663)
* fix:修复消息存档内红包祝福语和视频号部分字段数据类型声明错误的问题

* docs:更新注释说明

* fix:校正互通红包内的数据结构声明类型
2023-04-03 20:31:58 +08:00
Guo yongrong
8bc1474777 增加发送图文消息(点击跳转到图文消息页面)使用通过 “发布” 系列接口得到的 article_id (#664)
Co-authored-by: handson@intonead.com <gyr@3332502!>
2023-04-03 18:13:25 +08:00
markwang
ca0b74e082 企业微信保持代码风格统一,接口URL不导出 (#660)
* 企业微信-客户联系-统计管理

* 企业微信-客户联系-统计管理

* 企业微信-客户联系-统计管理

* debug

* rollback

* debug

* debug

* 获取用户信息

* token

* json.Marshal错误输出

* debug

* bugfix

* 企业微信-通讯录管理相关接口

* 企业微信-通讯录管理

* 企业微信-通讯录管理

* 企业微信-通讯录管理

* 企业微信-[联系我]方式新增和查询

* 企业微信-[联系我]方式新增和获取

* 企业微信-[联系我]方式更新

* 企业微信-[联系我]方式列表、删除

* json.Marshal错误输出

* 已实现接口bug修改

* 历史接口bugfix

* 历史接口bugfix

* comment

* 企业微信:客户联系-消息推送;素材管理-上传图片

* fix

* 企业微信-获取群发记录列表

* 历史接口bugfix

* 1.企业微信-客户联系-消息推送-入群欢迎语素材管理
2.企业微信-通讯录管理-成员管理-获取成员ID列表

* golangci-lint

* gofmt

* 方法访问命名

* 企业微信-批量获取客户详情入参优化

* 企业微信-通讯录管理-标签管理-创建/更新/删除标签

* 请求地址常量无需导出

* 企业微信保持代码风格统一,接口URL不导出

---------

Co-authored-by: wang.yu <wangyu@uniondrug.com>
Co-authored-by: markwang <www.wang61@qq.com>
2023-04-03 17:06:07 +08:00
markwang
fbda048f62 企业微信-通讯录管理-标签管理-创建/更新/删除标签 (#659) 2023-03-21 23:58:37 +08:00
markwang
494082ff4f 企业微信-批量获取客户详情入参优化 (#657)
* 企业微信-批量获取客户详情入参优化

---------

Co-authored-by: wang.yu <wangyu@uniondrug.com>
Co-authored-by: markwang <www.wang61@qq.com>
2023-03-14 19:18:20 +08:00
johnpoint
9c0189340b pref: 变量命名 & url (#650) 2023-02-14 12:04:44 +08:00
realpg
d39615f2fa 补全模板消息API中遗漏的 ‘防重入ID’ 参数 (#651)
client_msg_id:
防重入id。
对于同一个openid + client_msg_id, 只发送一条消息,10分钟有效,超过10分钟不保证效果。若无防重入需求,可不填
2023-02-11 23:50:33 +08:00
just5325
429219b53f 新增消息推送模块,实现部分常用消息推送:SendText 发送文本消息、SendImage 发送图片消息、SendVoice 发送语音消息 (#649)
企业微信消息推送接口:https://qyapi.weixin.qq.com/cgi-bin/message/send
2023-02-10 17:11:02 +08:00
johnpoint
6e80b71cb2 避免无效的access_token留在cache (#645) 2023-02-08 21:19:37 +08:00
Lien Li
ab354c4d03 feat: 支持企业微信上传临时素材 (#644) 2023-02-06 08:43:41 +08:00
daguang
d1d034eb95 oauth add snapshot user mark field (#643) 2023-01-29 11:58:00 +08:00
Lien Li
1a9dbc493b feat:支持客户联系的服务端事件 (#639)
https://developer.work.weixin.qq.com/document/path/92130
2023-01-04 10:26:16 +08:00
Lien Li
04559ed4bb fix:避免游标拼接异常导致取不到数据 (#638) 2023-01-03 10:07:45 +08:00
silenceper
abd583df01 fix #636 生成短链方法报commonError is invalid or not struct (#637) 2023-01-01 23:54:58 +08:00
34 changed files with 674 additions and 208 deletions

46
cache/cache.go vendored
View File

@@ -1,6 +1,9 @@
package cache
import "time"
import (
"context"
"time"
)
// Cache interface
type Cache interface {
@@ -9,3 +12,44 @@ type Cache interface {
IsExist(key string) bool
Delete(key string) error
}
// ContextCache interface
type ContextCache interface {
Cache
GetContext(ctx context.Context, key string) interface{}
SetContext(ctx context.Context, key string, val interface{}, timeout time.Duration) error
IsExistContext(ctx context.Context, key string) bool
DeleteContext(ctx context.Context, key string) error
}
// GetContext get value from cache
func GetContext(ctx context.Context, cache Cache, key string) interface{} {
if cache, ok := cache.(ContextCache); ok {
return cache.GetContext(ctx, key)
}
return cache.Get(key)
}
// SetContext set value to cache
func SetContext(ctx context.Context, cache Cache, key string, val interface{}, timeout time.Duration) error {
if cache, ok := cache.(ContextCache); ok {
return cache.SetContext(ctx, key, val, timeout)
}
return cache.Set(key, val, timeout)
}
// IsExistContext check value exists in cache.
func IsExistContext(ctx context.Context, cache Cache, key string) bool {
if cache, ok := cache.(ContextCache); ok {
return cache.IsExistContext(ctx, key)
}
return cache.IsExist(key)
}
// DeleteContext delete value in cache.
func DeleteContext(ctx context.Context, cache Cache, key string) error {
if cache, ok := cache.(ContextCache); ok {
return cache.DeleteContext(ctx, key)
}
return cache.Delete(key)
}

28
cache/redis.go vendored
View File

@@ -47,7 +47,12 @@ func (r *Redis) SetRedisCtx(ctx context.Context) {
// Get 获取一个值
func (r *Redis) Get(key string) interface{} {
result, err := r.conn.Do(r.ctx, "GET", key).Result()
return r.GetContext(r.ctx, key)
}
// GetContext 获取一个值
func (r *Redis) GetContext(ctx context.Context, key string) interface{} {
result, err := r.conn.Do(ctx, "GET", key).Result()
if err != nil {
return nil
}
@@ -56,17 +61,32 @@ func (r *Redis) Get(key string) interface{} {
// Set 设置一个值
func (r *Redis) Set(key string, val interface{}, timeout time.Duration) error {
return r.conn.SetEX(r.ctx, key, val, timeout).Err()
return r.SetContext(r.ctx, key, val, timeout)
}
// SetContext 设置一个值
func (r *Redis) SetContext(ctx context.Context, key string, val interface{}, timeout time.Duration) error {
return r.conn.SetEX(ctx, key, val, timeout).Err()
}
// IsExist 判断key是否存在
func (r *Redis) IsExist(key string) bool {
result, _ := r.conn.Exists(r.ctx, key).Result()
return r.IsExistContext(r.ctx, key)
}
// IsExistContext 判断key是否存在
func (r *Redis) IsExistContext(ctx context.Context, key string) bool {
result, _ := r.conn.Exists(ctx, key).Result()
return result > 0
}
// Delete 删除
func (r *Redis) Delete(key string) error {
return r.conn.Del(r.ctx, key).Err()
return r.DeleteContext(r.ctx, key)
}
// DeleteContext 删除
func (r *Redis) DeleteContext(ctx context.Context, key string) error {
return r.conn.Del(ctx, key).Err()
}

10
cache/redis_test.go vendored
View File

@@ -4,17 +4,23 @@ import (
"context"
"testing"
"time"
"github.com/alicebob/miniredis/v2"
)
func TestRedis(t *testing.T) {
server, err := miniredis.Run()
if err != nil {
t.Error("miniredis.Run Error", err)
}
t.Cleanup(server.Close)
var (
timeoutDuration = time.Second
ctx = context.Background()
opts = &RedisOpts{
Host: "127.0.0.1:6379",
Host: server.Addr(),
}
redis = NewRedis(ctx, opts)
err error
val = "silenceper"
key = "username"
)

1
go.mod
View File

@@ -3,6 +3,7 @@ module github.com/silenceper/wechat/v2
go 1.16
require (
github.com/alicebob/miniredis/v2 v2.30.0
github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d
github.com/fatih/structs v1.1.0
github.com/go-redis/redis/v8 v8.11.5

7
go.sum
View File

@@ -1,3 +1,7 @@
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk=
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
github.com/alicebob/miniredis/v2 v2.30.0 h1:uA3uhDbCxfO9+DI/DuGeAMr9qI+noVWwGPNTFuKID5M=
github.com/alicebob/miniredis/v2 v2.30.0/go.mod h1:84TWKZlxYkfgMucPBf5SOQBYJceZeQRFIaQgNMiCX6Q=
github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d h1:pVrfxiGfwelyab6n21ZBkbkmbevaf+WvMIiR7sr97hw=
github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
@@ -71,6 +75,8 @@ github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JT
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64 h1:5mLPGnFdSsevFRFc9q3yYbBkB6tsm4aCwwQV/j1JQAQ=
github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
@@ -89,6 +95,7 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

View File

@@ -37,7 +37,7 @@ type ShortLinker struct {
// resShortLinker 返回结构体
type resShortLinker struct {
// 通用错误
*util.CommonError
util.CommonError
// 返回的 shortLink
Link string `json:"link"`

View File

@@ -38,6 +38,7 @@ type CustomerMessage struct {
Wxcard *MediaWxcard `json:"wxcard,omitempty"` // 可选
Msgmenu *MediaMsgmenu `json:"msgmenu,omitempty"` // 可选
Miniprogrampage *MediaMiniprogrampage `json:"miniprogrampage,omitempty"` // 可选
Mpnewsarticle *MediaArticle `json:"mpnewsarticle,omitempty"` // 可选
}
// NewCustomerTextMessage 文本消息结构体构造方法
@@ -97,6 +98,11 @@ type MediaResource struct {
MediaID string `json:"media_id"`
}
// MediaArticle 消息使用的已发布文章id
type MediaArticle struct {
ArticleID string `json:"article_id"`
}
// MediaVideo 视频消息包含的内容
type MediaVideo struct {
MediaID string `json:"media_id"`

View File

@@ -29,11 +29,12 @@ func NewTemplate(context *context.Context) *Template {
// TemplateMessage 发送的模板消息内容
type TemplateMessage struct {
ToUser string `json:"touser"` // 必须, 接受者OpenID
TemplateID string `json:"template_id"` // 必须, 模版ID
URL string `json:"url,omitempty"` // 可选, 用户点击后跳转的URL, 该URL必须处于开发者在公众平台网站中设置的域中
Color string `json:"color,omitempty"` // 可选, 整个消息的颜色, 可以不设置
Data map[string]*TemplateDataItem `json:"data"` // 必须, 模板数据
ToUser string `json:"touser"` // 必须, 接受者OpenID
TemplateID string `json:"template_id"` // 必须, 模版ID
URL string `json:"url,omitempty"` // 可选, 用户点击后跳转的URL, 该URL必须处于开发者在公众平台网站中设置的域中
Color string `json:"color,omitempty"` // 可选, 整个消息的颜色, 可以不设置
Data map[string]*TemplateDataItem `json:"data"` // 必须, 模板数据
ClientMsgID string `json:"client_msg_id,omitempty"` // 可选, 防重入ID
MiniProgram struct {
AppID string `json:"appid"` // 所需跳转到的小程序appid该小程序appid必须与发模板消息的公众号是绑定关联关系

View File

@@ -64,6 +64,10 @@ type ResAccessToken struct {
OpenID string `json:"openid"`
Scope string `json:"scope"`
// IsSnapShotUser 是否为快照页模式虚拟账号只有当用户是快照页模式虚拟账号时返回值为1
// 公众号文档 https://developers.weixin.qq.com/community/minihome/doc/000c2c34068880629ced91a2f56001
IsSnapShotUser int `json:"is_snapshotuser"`
// UnionID 只有在用户将公众号绑定到微信开放平台帐号后,才会出现该字段。
// 公众号文档 https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842
UnionID string `json:"unionid"`

View File

@@ -154,134 +154,120 @@ func NewOCR(c *context.Context) *OCR {
}
// IDCard 身份证OCR识别接口
func (ocr *OCR) IDCard(path string) (ResIDCard ResIDCard, err error) {
func (ocr *OCR) IDCard(path string) (resIDCard ResIDCard, err error) {
accessToken, err := ocr.GetAccessToken()
if err != nil {
return
}
uri := fmt.Sprintf("%s?img_url=%s&access_token=%s", ocrIDCardURL, url.QueryEscape(path), accessToken)
response, err := util.HTTPPost(uri, "")
response, err := util.HTTPPost(fmt.Sprintf("%s?img_url=%s&access_token=%s", ocrIDCardURL, url.QueryEscape(path), accessToken), "")
if err != nil {
return
}
err = util.DecodeWithError(response, &ResIDCard, "OCRIDCard")
err = util.DecodeWithError(response, &resIDCard, "OCRIDCard")
return
}
// BankCard 银行卡OCR识别接口
func (ocr *OCR) BankCard(path string) (ResBankCard ResBankCard, err error) {
func (ocr *OCR) BankCard(path string) (resBankCard ResBankCard, err error) {
accessToken, err := ocr.GetAccessToken()
if err != nil {
return
}
uri := fmt.Sprintf("%s?img_url=%s&access_token=%s", ocrBankCardURL, url.QueryEscape(path), accessToken)
response, err := util.HTTPPost(uri, "")
response, err := util.HTTPPost(fmt.Sprintf("%s?img_url=%s&access_token=%s", ocrBankCardURL, url.QueryEscape(path), accessToken), "")
if err != nil {
return
}
err = util.DecodeWithError(response, &ResBankCard, "OCRBankCard")
err = util.DecodeWithError(response, &resBankCard, "OCRBankCard")
return
}
// Driving 行驶证OCR识别接口
func (ocr *OCR) Driving(path string) (ResDriving ResDriving, err error) {
func (ocr *OCR) Driving(path string) (resDriving ResDriving, err error) {
accessToken, err := ocr.GetAccessToken()
if err != nil {
return
}
uri := fmt.Sprintf("%s?img_url=%s&access_token=%s", ocrDrivingURL, url.QueryEscape(path), accessToken)
response, err := util.HTTPPost(uri, "")
response, err := util.HTTPPost(fmt.Sprintf("%s?img_url=%s&access_token=%s", ocrDrivingURL, url.QueryEscape(path), accessToken), "")
if err != nil {
return
}
err = util.DecodeWithError(response, &ResDriving, "OCRDriving")
err = util.DecodeWithError(response, &resDriving, "OCRDriving")
return
}
// DrivingLicense 驾驶证OCR识别接口
func (ocr *OCR) DrivingLicense(path string) (ResDrivingLicense ResDrivingLicense, err error) {
func (ocr *OCR) DrivingLicense(path string) (resDrivingLicense ResDrivingLicense, err error) {
accessToken, err := ocr.GetAccessToken()
if err != nil {
return
}
uri := fmt.Sprintf("%s?img_url=%s&access_token=%s", ocrDrivingLicenseURL, url.QueryEscape(path), accessToken)
response, err := util.HTTPPost(uri, "")
response, err := util.HTTPPost(fmt.Sprintf("%s?img_url=%s&access_token=%s", ocrDrivingLicenseURL, url.QueryEscape(path), accessToken), "")
if err != nil {
return
}
err = util.DecodeWithError(response, &ResDrivingLicense, "OCRDrivingLicense")
err = util.DecodeWithError(response, &resDrivingLicense, "OCRDrivingLicense")
return
}
// BizLicense 营业执照OCR识别接口
func (ocr *OCR) BizLicense(path string) (ResBizLicense ResBizLicense, err error) {
func (ocr *OCR) BizLicense(path string) (resBizLicense ResBizLicense, err error) {
accessToken, err := ocr.GetAccessToken()
if err != nil {
return
}
uri := fmt.Sprintf("%s?img_url=%s&access_token=%s", ocrBizLicenseURL, url.QueryEscape(path), accessToken)
response, err := util.HTTPPost(uri, "")
response, err := util.HTTPPost(fmt.Sprintf("%s?img_url=%s&access_token=%s", ocrBizLicenseURL, url.QueryEscape(path), accessToken), "")
if err != nil {
return
}
err = util.DecodeWithError(response, &ResBizLicense, "OCRBizLicense")
err = util.DecodeWithError(response, &resBizLicense, "OCRBizLicense")
return
}
// Common 通用印刷体OCR识别接口
func (ocr *OCR) Common(path string) (ResCommon ResCommon, err error) {
func (ocr *OCR) Common(path string) (resCommon ResCommon, err error) {
accessToken, err := ocr.GetAccessToken()
if err != nil {
return
}
uri := fmt.Sprintf("%s?img_url=%s&access_token=%s", ocrCommonURL, url.QueryEscape(path), accessToken)
response, err := util.HTTPPost(uri, "")
response, err := util.HTTPPost(fmt.Sprintf("%s?img_url=%s&access_token=%s", ocrCommonURL, url.QueryEscape(path), accessToken), "")
if err != nil {
return
}
err = util.DecodeWithError(response, &ResCommon, "OCRCommon")
err = util.DecodeWithError(response, &resCommon, "OCRCommon")
return
}
// PlateNumber 车牌OCR识别接口
func (ocr *OCR) PlateNumber(path string) (ResPlateNumber ResPlateNumber, err error) {
func (ocr *OCR) PlateNumber(path string) (resPlateNumber ResPlateNumber, err error) {
accessToken, err := ocr.GetAccessToken()
if err != nil {
return
}
uri := fmt.Sprintf("%s?img_url=%s&access_token=%s", ocrPlateNumberURL, url.QueryEscape(path), accessToken)
response, err := util.HTTPPost(uri, "")
response, err := util.HTTPPost(fmt.Sprintf("%s?img_url=%s&access_token=%s", ocrPlateNumberURL, url.QueryEscape(path), accessToken), "")
if err != nil {
return
}
err = util.DecodeWithError(response, &ResPlateNumber, "OCRPlateNumber")
err = util.DecodeWithError(response, &resPlateNumber, "OCRPlateNumber")
return
}

View File

@@ -2,11 +2,13 @@
package context
import (
"context"
"encoding/json"
"fmt"
"net/url"
"time"
"github.com/silenceper/wechat/v2/cache"
"github.com/silenceper/wechat/v2/util"
)
@@ -31,24 +33,29 @@ type ComponentAccessToken struct {
ExpiresIn int64 `json:"expires_in"`
}
// GetComponentAccessToken 获取 ComponentAccessToken
func (ctx *Context) GetComponentAccessToken() (string, error) {
// GetComponentAccessTokenContext 获取 ComponentAccessToken
func (ctx *Context) GetComponentAccessTokenContext(stdCtx context.Context) (string, error) {
accessTokenCacheKey := fmt.Sprintf("component_access_token_%s", ctx.AppID)
val := ctx.Cache.Get(accessTokenCacheKey)
val := cache.GetContext(stdCtx, ctx.Cache, accessTokenCacheKey)
if val == nil {
return "", fmt.Errorf("cann't get component access token")
}
return val.(string), nil
}
// SetComponentAccessToken 通过component_verify_ticket 获取 ComponentAccessToken
func (ctx *Context) SetComponentAccessToken(verifyTicket string) (*ComponentAccessToken, error) {
// GetComponentAccessToken 获取 ComponentAccessToken
func (ctx *Context) GetComponentAccessToken() (string, error) {
return ctx.GetComponentAccessTokenContext(context.Background())
}
// SetComponentAccessTokenContext 通过component_verify_ticket 获取 ComponentAccessToken
func (ctx *Context) SetComponentAccessTokenContext(stdCtx context.Context, verifyTicket string) (*ComponentAccessToken, error) {
body := map[string]string{
"component_appid": ctx.AppID,
"component_appsecret": ctx.AppSecret,
"component_verify_ticket": verifyTicket,
}
respBody, err := util.PostJSON(componentAccessTokenURL, body)
respBody, err := util.PostJSONContext(stdCtx, componentAccessTokenURL, body)
if err != nil {
return nil, err
}
@@ -64,15 +71,20 @@ func (ctx *Context) SetComponentAccessToken(verifyTicket string) (*ComponentAcce
accessTokenCacheKey := fmt.Sprintf("component_access_token_%s", ctx.AppID)
expires := at.ExpiresIn - 1500
if err := ctx.Cache.Set(accessTokenCacheKey, at.AccessToken, time.Duration(expires)*time.Second); err != nil {
if err := cache.SetContext(stdCtx, ctx.Cache, accessTokenCacheKey, at.AccessToken, time.Duration(expires)*time.Second); err != nil {
return nil, nil
}
return at, nil
}
// GetPreCode 获取预授权码
func (ctx *Context) GetPreCode() (string, error) {
cat, err := ctx.GetComponentAccessToken()
// SetComponentAccessToken 通过component_verify_ticket 获取 ComponentAccessToken
func (ctx *Context) SetComponentAccessToken(stdCtx context.Context, verifyTicket string) (*ComponentAccessToken, error) {
return ctx.SetComponentAccessTokenContext(stdCtx, verifyTicket)
}
// GetPreCodeContext 获取预授权码
func (ctx *Context) GetPreCodeContext(stdCtx context.Context) (string, error) {
cat, err := ctx.GetComponentAccessTokenContext(stdCtx)
if err != nil {
return "", err
}
@@ -80,7 +92,7 @@ func (ctx *Context) GetPreCode() (string, error) {
"component_appid": ctx.AppID,
}
uri := fmt.Sprintf(getPreCodeURL, cat)
body, err := util.PostJSON(uri, req)
body, err := util.PostJSONContext(stdCtx, uri, req)
if err != nil {
return "", err
}
@@ -95,24 +107,39 @@ func (ctx *Context) GetPreCode() (string, error) {
return ret.PreCode, nil
}
// GetComponentLoginPage 获取第三方公众号授权链接(扫码授权)
func (ctx *Context) GetComponentLoginPage(redirectURI string, authType int, bizAppID string) (string, error) {
code, err := ctx.GetPreCode()
// GetPreCode 获取预授权码
func (ctx *Context) GetPreCode() (string, error) {
return ctx.GetPreCodeContext(context.Background())
}
// GetComponentLoginPageContext 获取第三方公众号授权链接(扫码授权)
func (ctx *Context) GetComponentLoginPageContext(stdCtx context.Context, redirectURI string, authType int, bizAppID string) (string, error) {
code, err := ctx.GetPreCodeContext(stdCtx)
if err != nil {
return "", err
}
return fmt.Sprintf(componentLoginURL, ctx.AppID, code, url.QueryEscape(redirectURI), authType, bizAppID), nil
}
// GetBindComponentURL 获取第三方公众号授权链接(链接跳转,适用移动端)
func (ctx *Context) GetBindComponentURL(redirectURI string, authType int, bizAppID string) (string, error) {
code, err := ctx.GetPreCode()
// GetComponentLoginPage 获取第三方公众号授权链接(扫码授权)
func (ctx *Context) GetComponentLoginPage(redirectURI string, authType int, bizAppID string) (string, error) {
return ctx.GetComponentLoginPageContext(context.Background(), redirectURI, authType, bizAppID)
}
// GetBindComponentURLContext 获取第三方公众号授权链接(链接跳转,适用移动端)
func (ctx *Context) GetBindComponentURLContext(stdCtx context.Context, redirectURI string, authType int, bizAppID string) (string, error) {
code, err := ctx.GetPreCodeContext(stdCtx)
if err != nil {
return "", err
}
return fmt.Sprintf(bindComponentURL, authType, ctx.AppID, code, url.QueryEscape(redirectURI), bizAppID), nil
}
// GetBindComponentURL 获取第三方公众号授权链接(链接跳转,适用移动端)
func (ctx *Context) GetBindComponentURL(redirectURI string, authType int, bizAppID string) (string, error) {
return ctx.GetBindComponentURLContext(context.Background(), redirectURI, authType, bizAppID)
}
// ID 微信返回接口中各种类型字段
type ID struct {
ID int `json:"id"`
@@ -137,9 +164,9 @@ type AuthrAccessToken struct {
RefreshToken string `json:"authorizer_refresh_token"`
}
// QueryAuthCode 使用授权码换取公众号或小程序的接口调用凭据和授权信息
func (ctx *Context) QueryAuthCode(authCode string) (*AuthBaseInfo, error) {
cat, err := ctx.GetComponentAccessToken()
// QueryAuthCodeContext 使用授权码换取公众号或小程序的接口调用凭据和授权信息
func (ctx *Context) QueryAuthCodeContext(stdCtx context.Context, authCode string) (*AuthBaseInfo, error) {
cat, err := ctx.GetComponentAccessTokenContext(stdCtx)
if err != nil {
return nil, err
}
@@ -149,7 +176,7 @@ func (ctx *Context) QueryAuthCode(authCode string) (*AuthBaseInfo, error) {
"authorization_code": authCode,
}
uri := fmt.Sprintf(queryAuthURL, cat)
body, err := util.PostJSON(uri, req)
body, err := util.PostJSONContext(stdCtx, uri, req)
if err != nil {
return nil, err
}
@@ -169,9 +196,14 @@ func (ctx *Context) QueryAuthCode(authCode string) (*AuthBaseInfo, error) {
return ret.Info, nil
}
// RefreshAuthrToken 获取(刷新)授权公众号或小程序的接口调用凭据(令牌)
func (ctx *Context) RefreshAuthrToken(appid, refreshToken string) (*AuthrAccessToken, error) {
cat, err := ctx.GetComponentAccessToken()
// QueryAuthCode 使用授权码换取公众号或小程序的接口调用凭据和授权信息
func (ctx *Context) QueryAuthCode(authCode string) (*AuthBaseInfo, error) {
return ctx.QueryAuthCodeContext(context.Background(), authCode)
}
// RefreshAuthrTokenContext 获取(刷新)授权公众号或小程序的接口调用凭据(令牌)
func (ctx *Context) RefreshAuthrTokenContext(stdCtx context.Context, appid, refreshToken string) (*AuthrAccessToken, error) {
cat, err := ctx.GetComponentAccessTokenContext(stdCtx)
if err != nil {
return nil, err
}
@@ -182,7 +214,7 @@ func (ctx *Context) RefreshAuthrToken(appid, refreshToken string) (*AuthrAccessT
"authorizer_refresh_token": refreshToken,
}
uri := fmt.Sprintf(refreshTokenURL, cat)
body, err := util.PostJSON(uri, req)
body, err := util.PostJSONContext(stdCtx, uri, req)
if err != nil {
return nil, err
}
@@ -193,22 +225,32 @@ func (ctx *Context) RefreshAuthrToken(appid, refreshToken string) (*AuthrAccessT
}
authrTokenKey := "authorizer_access_token_" + appid
if err := ctx.Cache.Set(authrTokenKey, ret.AccessToken, time.Minute*80); err != nil {
if err := cache.SetContext(stdCtx, ctx.Cache, authrTokenKey, ret.AccessToken, time.Second*time.Duration(ret.ExpiresIn-30)); err != nil {
return nil, err
}
return ret, nil
}
// GetAuthrAccessToken 获取授权方AccessToken
func (ctx *Context) GetAuthrAccessToken(appid string) (string, error) {
// RefreshAuthrToken 获取(刷新)授权公众号或小程序的接口调用凭据(令牌)
func (ctx *Context) RefreshAuthrToken(appid, refreshToken string) (*AuthrAccessToken, error) {
return ctx.RefreshAuthrTokenContext(context.Background(), appid, refreshToken)
}
// GetAuthrAccessTokenContext 获取授权方AccessToken
func (ctx *Context) GetAuthrAccessTokenContext(stdCtx context.Context, appid string) (string, error) {
authrTokenKey := "authorizer_access_token_" + appid
val := ctx.Cache.Get(authrTokenKey)
val := cache.GetContext(stdCtx, ctx.Cache, authrTokenKey)
if val == nil {
return "", fmt.Errorf("cannot get authorizer %s access token", appid)
}
return val.(string), nil
}
// GetAuthrAccessToken 获取授权方AccessToken
func (ctx *Context) GetAuthrAccessToken(appid string) (string, error) {
return ctx.GetAuthrAccessTokenContext(context.Background(), appid)
}
// AuthorizerInfo 授权方详细信息
type AuthorizerInfo struct {
NickName string `json:"nick_name"`
@@ -258,9 +300,9 @@ type CategoriesInfo struct {
Second string `wx:"second"`
}
// GetAuthrInfo 获取授权方的帐号基本信息
func (ctx *Context) GetAuthrInfo(appid string) (*AuthorizerInfo, *AuthBaseInfo, error) {
cat, err := ctx.GetComponentAccessToken()
// GetAuthrInfoContext 获取授权方的帐号基本信息
func (ctx *Context) GetAuthrInfoContext(stdCtx context.Context, appid string) (*AuthorizerInfo, *AuthBaseInfo, error) {
cat, err := ctx.GetComponentAccessTokenContext(stdCtx)
if err != nil {
return nil, nil, err
}
@@ -271,7 +313,7 @@ func (ctx *Context) GetAuthrInfo(appid string) (*AuthorizerInfo, *AuthBaseInfo,
}
uri := fmt.Sprintf(getComponentInfoURL, cat)
body, err := util.PostJSON(uri, req)
body, err := util.PostJSONContext(stdCtx, uri, req)
if err != nil {
return nil, nil, err
}
@@ -286,3 +328,8 @@ func (ctx *Context) GetAuthrInfo(appid string) (*AuthorizerInfo, *AuthBaseInfo,
return ret.AuthorizerInfo, ret.AuthorizationInfo, nil
}
// GetAuthrInfo 获取授权方的帐号基本信息
func (ctx *Context) GetAuthrInfo(appid string) (*AuthorizerInfo, *AuthBaseInfo, error) {
return ctx.GetAuthrInfoContext(context.Background(), appid)
}

View File

@@ -18,9 +18,6 @@ type OpenPlatform struct {
// NewOpenPlatform new openplatform
func NewOpenPlatform(cfg *config.Config) *OpenPlatform {
if cfg.Cache == nil {
panic("cache 未设置")
}
ctx := &context.Context{
Config: cfg,
}

View File

@@ -69,8 +69,8 @@ func HTTPPostContext(ctx context.Context, uri string, data []byte, header map[st
return io.ReadAll(response.Body)
}
// PostJSON post json 数据请求
func PostJSON(uri string, obj interface{}) ([]byte, error) {
// PostJSONContext post json 数据请求
func PostJSONContext(ctx context.Context, uri string, obj interface{}) ([]byte, error) {
jsonBuf := new(bytes.Buffer)
enc := json.NewEncoder(jsonBuf)
enc.SetEscapeHTML(false)
@@ -78,7 +78,12 @@ func PostJSON(uri string, obj interface{}) ([]byte, error) {
if err != nil {
return nil, err
}
response, err := http.Post(uri, "application/json;charset=utf-8", jsonBuf)
req, err := http.NewRequestWithContext(ctx, "POST", uri, jsonBuf)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json;charset=utf-8")
response, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
@@ -90,6 +95,11 @@ func PostJSON(uri string, obj interface{}) ([]byte, error) {
return io.ReadAll(response.Body)
}
// PostJSON post json 数据请求
func PostJSON(uri string, obj interface{}) ([]byte, error) {
return PostJSONContext(context.Background(), uri, obj)
}
// PostJSONWithRespContentType post json数据请求且返回数据类型
func PostJSONWithRespContentType(uri string, obj interface{}) ([]byte, string, error) {
jsonBuf := new(bytes.Buffer)

View File

@@ -68,10 +68,16 @@ func (wc *Wechat) GetPay(cfg *payConfig.Config) *pay.Pay {
// GetOpenPlatform 获取微信开放平台的实例
func (wc *Wechat) GetOpenPlatform(cfg *openConfig.Config) *openplatform.OpenPlatform {
if cfg.Cache == nil {
cfg.Cache = wc.cache
}
return openplatform.NewOpenPlatform(cfg)
}
// GetWork 获取企业微信的实例
func (wc *Wechat) GetWork(cfg *workConfig.Config) *work.Work {
if cfg.Cache == nil {
cfg.Cache = wc.cache
}
return work.NewWork(cfg)
}

View File

@@ -7,8 +7,8 @@ import (
)
const (
// DepartmentSimpleListURL 获取子部门ID列表
DepartmentSimpleListURL = "https://qyapi.weixin.qq.com/cgi-bin/department/simplelist?access_token=%s&id=%d"
// departmentSimpleListURL 获取子部门ID列表
departmentSimpleListURL = "https://qyapi.weixin.qq.com/cgi-bin/department/simplelist?access_token=%s&id=%d"
)
type (
@@ -36,7 +36,7 @@ func (r *Client) DepartmentSimpleList(departmentID int) ([]*DepartmentID, error)
return nil, err
}
var response []byte
if response, err = util.HTTPGet(fmt.Sprintf(DepartmentSimpleListURL, accessToken, departmentID)); err != nil {
if response, err = util.HTTPGet(fmt.Sprintf(departmentSimpleListURL, accessToken, departmentID)); err != nil {
return nil, err
}
result := &DepartmentSimpleListResponse{}

92
work/addresslist/tag.go Normal file
View File

@@ -0,0 +1,92 @@
package addresslist
import (
"fmt"
"github.com/silenceper/wechat/v2/util"
)
const (
// createTagURL 创建标签
createTagURL = "https://qyapi.weixin.qq.com/cgi-bin/tag/create?access_token=%s"
// updateTagURL 更新标签名字
updateTagURL = "https://qyapi.weixin.qq.com/cgi-bin/tag/update?access_token=%s"
// deleteTagURL 删除标签
deleteTagURL = "https://qyapi.weixin.qq.com/cgi-bin/tag/delete?access_token=%s&tagid=%d"
)
type (
// CreateTagRequest 创建标签请求
CreateTagRequest struct {
TagName string `json:"tagname"`
TagID int `json:"tagid,omitempty"`
}
// CreateTagResponse 创建标签响应
CreateTagResponse struct {
util.CommonError
TagID int `json:"tagid"`
}
)
// CreateTag 创建标签
// see https://developer.work.weixin.qq.com/document/path/90210
func (r *Client) CreateTag(req *CreateTagRequest) (*CreateTagResponse, 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(createTagURL, accessToken), req); err != nil {
return nil, err
}
result := &CreateTagResponse{}
if err = util.DecodeWithError(response, result, "CreateTag"); err != nil {
return nil, err
}
return result, nil
}
type (
// UpdateTagRequest 更新标签名字请求
UpdateTagRequest struct {
TagID int `json:"tagid"`
TagName string `json:"tagname"`
}
)
// UpdateTag 更新标签名字
// see https://developer.work.weixin.qq.com/document/path/90211
func (r *Client) UpdateTag(req *UpdateTagRequest) 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(updateTagURL, accessToken), req); err != nil {
return err
}
return util.DecodeWithCommonError(response, "UpdateTag")
}
// DeleteTag 删除标签
// @see https://developer.work.weixin.qq.com/document/path/90212
func (r *Client) DeleteTag(tagID int) error {
var (
accessToken string
err error
)
if accessToken, err = r.GetAccessToken(); err != nil {
return err
}
var response []byte
if response, err = util.HTTPGet(fmt.Sprintf(deleteTagURL, accessToken, tagID)); err != nil {
return err
}
return util.DecodeWithCommonError(response, "DeleteTag")
}

View File

@@ -7,12 +7,12 @@ import (
)
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"
// 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 (
@@ -41,7 +41,7 @@ func (r *Client) UserSimpleList(departmentID int) ([]*UserList, error) {
return nil, err
}
var response []byte
if response, err = util.HTTPGet(fmt.Sprintf(UserSimpleListURL, accessToken, departmentID)); err != nil {
if response, err = util.HTTPGet(fmt.Sprintf(userSimpleListURL, accessToken, departmentID)); err != nil {
return nil, err
}
result := &UserSimpleListResponse{}
@@ -125,7 +125,7 @@ func (r *Client) UserGet(UserID string) (*UserGetResponse, error) {
return nil, err
}
var response []byte
if response, err = util.HTTPGet(fmt.Sprintf(UserGetURL, accessToken, UserID)); err != nil {
if response, err = util.HTTPGet(fmt.Sprintf(userGetURL, accessToken, UserID)); err != nil {
return nil, err
}
result := &UserGetResponse{}
@@ -166,7 +166,7 @@ func (r *Client) UserListID(req *UserListIDRequest) (*UserListIDResponse, error)
return nil, err
}
var response []byte
if response, err = util.PostJSON(fmt.Sprintf(UserListIDURL, accessToken), req); err != nil {
if response, err = util.PostJSON(fmt.Sprintf(userListIDURL, accessToken), req); err != nil {
return nil, err
}
result := &UserListIDResponse{}

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

@@ -7,16 +7,16 @@ import (
)
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"
// 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 (
@@ -98,7 +98,7 @@ func (r *Client) AddContactWay(req *AddContactWayRequest) (*AddContactWayRespons
return nil, err
}
var response []byte
if response, err = util.PostJSON(fmt.Sprintf(AddContactWayURL, accessToken), req); err != nil {
if response, err = util.PostJSON(fmt.Sprintf(addContactWayURL, accessToken), req); err != nil {
return nil, err
}
result := &AddContactWayResponse{}
@@ -149,7 +149,7 @@ func (r *Client) GetContactWay(req *GetContactWayRequest) (*GetContactWayRespons
return nil, err
}
var response []byte
if response, err = util.PostJSON(fmt.Sprintf(GetContactWayURL, accessToken), req); err != nil {
if response, err = util.PostJSON(fmt.Sprintf(getContactWayURL, accessToken), req); err != nil {
return nil, err
}
result := &GetContactWayResponse{}
@@ -191,7 +191,7 @@ func (r *Client) UpdateContactWay(req *UpdateContactWayRequest) (*UpdateContactW
return nil, err
}
var response []byte
if response, err = util.PostJSON(fmt.Sprintf(UpdateContactWayURL, accessToken), req); err != nil {
if response, err = util.PostJSON(fmt.Sprintf(updateContactWayURL, accessToken), req); err != nil {
return nil, err
}
result := &UpdateContactWayResponse{}
@@ -232,7 +232,7 @@ func (r *Client) ListContactWay(req *ListContactWayRequest) (*ListContactWayResp
return nil, err
}
var response []byte
if response, err = util.PostJSON(fmt.Sprintf(ListContactWayURL, accessToken), req); err != nil {
if response, err = util.PostJSON(fmt.Sprintf(listContactWayURL, accessToken), req); err != nil {
return nil, err
}
result := &ListContactWayResponse{}
@@ -264,7 +264,7 @@ func (r *Client) DelContactWay(req *DelContactWayRequest) (*DelContactWayRespons
return nil, err
}
var response []byte
if response, err = util.PostJSON(fmt.Sprintf(DelContactWayURL, accessToken), req); err != nil {
if response, err = util.PostJSON(fmt.Sprintf(delContactWayURL, accessToken), req); err != nil {
return nil, err
}
result := &DelContactWayResponse{}

View File

@@ -8,14 +8,14 @@ import (
)
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"
// 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 外部联系人列表响应
@@ -32,7 +32,7 @@ func (r *Client) GetExternalUserList(userID string) ([]string, error) {
return nil, err
}
var response []byte
response, err = util.HTTPGet(fmt.Sprintf("%s?access_token=%v&userid=%v", FetchExternalContactUserListURL, accessToken, userID))
response, err = util.HTTPGet(fmt.Sprintf("%s?access_token=%v&userid=%v", fetchExternalContactUserListURL, accessToken, userID))
if err != nil {
return nil, err
}
@@ -103,7 +103,11 @@ func (r *Client) GetExternalUserDetail(externalUserID string, nextCursor ...stri
return nil, err
}
var response []byte
response, err = util.HTTPGet(fmt.Sprintf("%s?access_token=%v&external_userid=%v&cursor=%v", FetchExternalContactUserDetailURL, accessToken, externalUserID, nextCursor))
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
}
@@ -119,6 +123,7 @@ func (r *Client) GetExternalUserDetail(externalUserID string, nextCursor ...stri
type BatchGetExternalUserDetailsRequest struct {
UserIDList []string `json:"userid_list"`
Cursor string `json:"cursor"`
Limit int `json:"limit,omitempty"`
}
// ExternalUserDetailListResponse 批量获取外部联系人详情响应
@@ -173,7 +178,7 @@ func (r *Client) BatchGetExternalUserDetails(request BatchGetExternalUserDetails
if err != nil {
return nil, err
}
response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", FetchBatchExternalContactUserDetailURL, accessToken), string(jsonData))
response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", fetchBatchExternalContactUserDetailURL, accessToken), string(jsonData))
if err != nil {
return nil, err
}
@@ -208,7 +213,7 @@ func (r *Client) UpdateUserRemark(request UpdateUserRemarkRequest) error {
if err != nil {
return err
}
response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", UpdateUserRemarkURL, accessToken), string(jsonData))
response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", updateUserRemarkURL, accessToken), string(jsonData))
if err != nil {
return err
}

View File

@@ -7,8 +7,8 @@ import (
)
const (
// FetchFollowUserListURL 获取配置了客户联系功能的成员列表
FetchFollowUserListURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get_follow_user_list"
// fetchFollowUserListURL 获取配置了客户联系功能的成员列表
fetchFollowUserListURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get_follow_user_list"
)
// followerUserResponse 客户联系功能的成员列表响应
@@ -25,7 +25,7 @@ func (r *Client) GetFollowUserList() ([]string, error) {
return nil, err
}
var response []byte
response, err = util.HTTPGet(fmt.Sprintf("%s?access_token=%s", FetchFollowUserListURL, accessToken))
response, err = util.HTTPGet(fmt.Sprintf("%s?access_token=%s", fetchFollowUserListURL, accessToken))
if err != nil {
return nil, err
}

View File

@@ -6,8 +6,8 @@ import (
"github.com/silenceper/wechat/v2/util"
)
// OpengIDToChatIDURL 客户群opengid转换URL
const OpengIDToChatIDURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/opengid_to_chatid"
// opengIDToChatIDURL 客户群opengid转换URL
const opengIDToChatIDURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/opengid_to_chatid"
type (
//GroupChatListRequest 获取客户群列表的请求参数
@@ -39,7 +39,7 @@ func (r *Client) GetGroupChatList(req *GroupChatListRequest) (*GroupChatListResp
return nil, err
}
var response []byte
response, err = util.PostJSON(fmt.Sprintf("%s/list?access_token=%s", GroupChatURL, accessToken), req)
response, err = util.PostJSON(fmt.Sprintf("%s/list?access_token=%s", groupChatURL, accessToken), req)
if err != nil {
return nil, err
}
@@ -100,7 +100,7 @@ func (r *Client) GetGroupChatDetail(req *GroupChatDetailRequest) (*GroupChatDeta
return nil, err
}
var response []byte
response, err = util.PostJSON(fmt.Sprintf("%s/get?access_token=%s", GroupChatURL, accessToken), req)
response, err = util.PostJSON(fmt.Sprintf("%s/get?access_token=%s", groupChatURL, accessToken), req)
if err != nil {
return nil, err
}
@@ -131,7 +131,7 @@ func (r *Client) OpengIDToChatID(req *OpengIDToChatIDRequest) (*OpengIDToChatIDR
return nil, err
}
var response []byte
response, err = util.PostJSON(fmt.Sprintf("%s?access_token=%s", OpengIDToChatIDURL, accessToken), req)
response, err = util.PostJSON(fmt.Sprintf("%s?access_token=%s", opengIDToChatIDURL, accessToken), req)
if err != nil {
return nil, err
}

View File

@@ -6,8 +6,8 @@ import (
"github.com/silenceper/wechat/v2/util"
)
// GroupChatURL 客户群
const GroupChatURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/groupchat"
// groupChatURL 客户群
const groupChatURL = "https://qyapi.weixin.qq.com/cgi-bin/externalcontact/groupchat"
type (
// AddJoinWayRequest 添加群配置请求参数
@@ -39,7 +39,7 @@ func (r *Client) AddJoinWay(req *AddJoinWayRequest) (*AddJoinWayResponse, error)
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)
response, err = util.PostJSON(fmt.Sprintf("%s/add_join_way?access_token=%s", groupChatURL, accessToken), req)
if err != nil {
return nil, err
}
@@ -86,7 +86,7 @@ func (r *Client) GetJoinWay(req *JoinWayConfigRequest) (*GetJoinWayResponse, err
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)
response, err = util.PostJSON(fmt.Sprintf("%s/get_join_way?access_token=%s", groupChatURL, accessToken), req)
if err != nil {
return nil, err
}
@@ -120,7 +120,7 @@ func (r *Client) UpdateJoinWay(req *UpdateJoinWayRequest) error {
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)
response, err = util.PostJSON(fmt.Sprintf("%s/update_join_way?access_token=%s", groupChatURL, accessToken), req)
if err != nil {
return err
}
@@ -138,7 +138,7 @@ func (r *Client) DelJoinWay(req *JoinWayConfigRequest) error {
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)
response, err = util.PostJSON(fmt.Sprintf("%s/del_join_way?access_token=%s", groupChatURL, accessToken), req)
if err != nil {
return err
}

View File

@@ -7,24 +7,24 @@ import (
)
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"
// 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 创建企业群发请求
@@ -98,7 +98,7 @@ func (r *Client) AddMsgTemplate(req *AddMsgTemplateRequest) (*AddMsgTemplateResp
return nil, err
}
var response []byte
if response, err = util.PostJSON(fmt.Sprintf(AddMsgTemplateURL, accessToken), req); err != nil {
if response, err = util.PostJSON(fmt.Sprintf(addMsgTemplateURL, accessToken), req); err != nil {
return nil, err
}
result := &AddMsgTemplateResponse{}
@@ -147,7 +147,7 @@ func (r *Client) GetGroupMsgListV2(req *GetGroupMsgListV2Request) (*GetGroupMsgL
return nil, err
}
var response []byte
if response, err = util.PostJSON(fmt.Sprintf(GetGroupMsgListV2URL, accessToken), req); err != nil {
if response, err = util.PostJSON(fmt.Sprintf(getGroupMsgListV2URL, accessToken), req); err != nil {
return nil, err
}
result := &GetGroupMsgListV2Response{}
@@ -189,7 +189,7 @@ func (r *Client) GetGroupMsgTask(req *GetGroupMsgTaskRequest) (*GetGroupMsgTaskR
return nil, err
}
var response []byte
if response, err = util.PostJSON(fmt.Sprintf(GetGroupMsgTaskURL, accessToken), req); err != nil {
if response, err = util.PostJSON(fmt.Sprintf(getGroupMsgTaskURL, accessToken), req); err != nil {
return nil, err
}
result := &GetGroupMsgTaskResponse{}
@@ -234,7 +234,7 @@ func (r *Client) GetGroupMsgSendResult(req *GetGroupMsgSendResultRequest) (*GetG
return nil, err
}
var response []byte
if response, err = util.PostJSON(fmt.Sprintf(GetGroupMsgSendResultURL, accessToken), req); err != nil {
if response, err = util.PostJSON(fmt.Sprintf(getGroupMsgSendResultURL, accessToken), req); err != nil {
return nil, err
}
result := &GetGroupMsgSendResultResponse{}
@@ -267,7 +267,7 @@ func (r *Client) SendWelcomeMsg(req *SendWelcomeMsgRequest) error {
return err
}
var response []byte
if response, err = util.PostJSON(fmt.Sprintf(SendWelcomeMsgURL, accessToken), req); err != nil {
if response, err = util.PostJSON(fmt.Sprintf(sendWelcomeMsgURL, accessToken), req); err != nil {
return err
}
result := &SendWelcomeMsgResponse{}
@@ -306,7 +306,7 @@ func (r *Client) AddGroupWelcomeTemplate(req *AddGroupWelcomeTemplateRequest) (*
return nil, err
}
var response []byte
if response, err = util.PostJSON(fmt.Sprintf(AddGroupWelcomeTemplateURL, accessToken), req); err != nil {
if response, err = util.PostJSON(fmt.Sprintf(addGroupWelcomeTemplateURL, accessToken), req); err != nil {
return nil, err
}
result := &AddGroupWelcomeTemplateResponse{}
@@ -344,7 +344,7 @@ func (r *Client) EditGroupWelcomeTemplate(req *EditGroupWelcomeTemplateRequest)
return err
}
var response []byte
if response, err = util.PostJSON(fmt.Sprintf(EditGroupWelcomeTemplateURL, accessToken), req); err != nil {
if response, err = util.PostJSON(fmt.Sprintf(editGroupWelcomeTemplateURL, accessToken), req); err != nil {
return err
}
result := &EditGroupWelcomeTemplateResponse{}
@@ -381,7 +381,7 @@ func (r *Client) GetGroupWelcomeTemplate(req *GetGroupWelcomeTemplateRequest) (*
return nil, err
}
var response []byte
if response, err = util.PostJSON(fmt.Sprintf(GetGroupWelcomeTemplateURL, accessToken), req); err != nil {
if response, err = util.PostJSON(fmt.Sprintf(getGroupWelcomeTemplateURL, accessToken), req); err != nil {
return nil, err
}
result := &GetGroupWelcomeTemplateResponse{}
@@ -413,7 +413,7 @@ func (r *Client) DelGroupWelcomeTemplate(req *DelGroupWelcomeTemplateRequest) er
return err
}
var response []byte
if response, err = util.PostJSON(fmt.Sprintf(DelGroupWelcomeTemplateURL, accessToken), req); err != nil {
if response, err = util.PostJSON(fmt.Sprintf(delGroupWelcomeTemplateURL, accessToken), req); err != nil {
return err
}
result := &DelGroupWelcomeTemplateResponse{}

View File

@@ -8,12 +8,12 @@ import (
)
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"
// 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 (
@@ -54,7 +54,7 @@ func (r *Client) GetUserBehaviorData(req *GetUserBehaviorRequest) ([]BehaviorDat
if err != nil {
return nil, err
}
response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", GetUserBehaviorDataURL, accessToken), string(jsonData))
response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", getUserBehaviorDataURL, accessToken), string(jsonData))
if err != nil {
return nil, err
}
@@ -120,7 +120,7 @@ func (r *Client) GetGroupChatStat(req *GetGroupChatStatRequest) (*GetGroupChatSt
if err != nil {
return nil, err
}
response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", GetGroupChatStatURL, accessToken), string(jsonData))
response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", getGroupChatStatURL, accessToken), string(jsonData))
if err != nil {
return nil, err
}
@@ -163,7 +163,7 @@ func (r *Client) GetGroupChatStatByDay(req *GetGroupChatStatByDayRequest) ([]Get
if err != nil {
return nil, err
}
response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", GetGroupChatStatByDayURL, accessToken), string(jsonData))
response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", getGroupChatStatByDayURL, accessToken), string(jsonData))
if err != nil {
return nil, err
}

View File

@@ -8,16 +8,16 @@ import (
)
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"
// 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 获取企业标签请求
@@ -63,7 +63,7 @@ func (r *Client) GetCropTagList(req GetCropTagRequest) ([]TagGroup, error) {
if err != nil {
return nil, err
}
response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", GetCropTagURL, accessToken), string(jsonData))
response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", getCropTagURL, accessToken), string(jsonData))
if err != nil {
return nil, err
}
@@ -109,7 +109,7 @@ func (r *Client) AddCropTag(req AddCropTagRequest) (*TagGroup, error) {
if err != nil {
return nil, err
}
response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", AddCropTagURL, accessToken), string(jsonData))
response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", addCropTagURL, accessToken), string(jsonData))
if err != nil {
return nil, err
}
@@ -141,7 +141,7 @@ func (r *Client) EditCropTag(req EditCropTagRequest) error {
if err != nil {
return err
}
response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", EditCropTagURL, accessToken), string(jsonData))
response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", editCropTagURL, accessToken), string(jsonData))
if err != nil {
return err
}
@@ -167,7 +167,7 @@ func (r *Client) DeleteCropTag(req DeleteCropTagRequest) error {
if err != nil {
return err
}
response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", DelCropTagURL, accessToken), string(jsonData))
response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", delCropTagURL, accessToken), string(jsonData))
if err != nil {
return err
}
@@ -195,7 +195,7 @@ func (r *Client) MarkTag(request MarkTagRequest) error {
if err != nil {
return err
}
response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", MarkCropTagURL, accessToken), string(jsonData))
response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", markCropTagURL, accessToken), string(jsonData))
if err != nil {
return err
}

View File

@@ -57,6 +57,7 @@ type CallbackMessage struct {
MsgType string `json:"msgtype"` // 消息的类型,此时固定为 event
Event string `json:"event"` // 事件的类型,此时固定为 kf_msg_or_event
Token string `json:"token"` // 调用拉取消息接口时需要传此token用于校验请求的合法性
OpenKfID string `json:"open_kfid"` // 有新消息的客服帐号。可通过sync_msg接口指定open_kfid获取此客服帐号的消息
}
// GetCallbackMessage 获取回调事件中的消息内容

View File

@@ -16,9 +16,11 @@ const (
// SyncMsgOptions 获取消息查询参数
type SyncMsgOptions struct {
Cursor string `json:"cursor"` // 上一次调用时返回的next_cursor第一次拉取可以不填, 不多于64字节
Token string `json:"token"` // 回调事件返回的token字段10分钟内有效可不填如果不填接口有严格的频率限制, 不多于128字节
Limit uint `json:"limit"` // 期望请求的数据量默认值和最大值都为1000, 注意可能会出现返回条数少于limit的情况需结合返回的has_more字段判断是否继续请求。
Cursor string `json:"cursor"` // 上一次调用时返回的next_cursor第一次拉取可以不填, 不多于64字节
Token string `json:"token"` // 回调事件返回的token字段10分钟内有效可不填如果不填接口有严格的频率限制, 不多于128字节
Limit uint `json:"limit"` // 期望请求的数据量默认值和最大值都为1000, 注意可能会出现返回条数少于limit的情况需结合返回的has_more字段判断是否继续请求。
VoiceFormat uint `json:"voice_format"` // 语音消息类型0-Amr 1-Silk默认0。可通过该参数控制返回的语音格式开发者可按需选择自己程序支持的一种格式
OpenKfID string `json:"open_kfid"` // 指定拉取某个客服帐号的消息否则默认返回有权限的客服帐号的消息。当客服帐号较多建议按open_kfid来拉取以获取更好的性能。
}
// SyncMsgSchema 获取消息查询响应内容

View File

@@ -7,8 +7,10 @@ import (
)
const (
// UploadImgURL 上传图片
UploadImgURL = "https://qyapi.weixin.qq.com/cgi-bin/media/uploadimg?access_token=%s"
// uploadImgURL 上传图片
uploadImgURL = "https://qyapi.weixin.qq.com/cgi-bin/media/uploadimg?access_token=%s"
// uploadTempFile 上传临时素材
uploadTempFile = "https://qyapi.weixin.qq.com/cgi-bin/media/upload?access_token=%s&type=%s"
)
// UploadImgResponse 上传图片响应
@@ -17,6 +19,14 @@ type UploadImgResponse struct {
URL string `json:"url"`
}
// UploadTempFileResponse 上传临时素材响应
type UploadTempFileResponse struct {
util.CommonError
MediaID string `json:"media_id"`
CreateAt string `json:"created_at"`
Type string `json:"type"`
}
// UploadImg 上传图片
// @see https://developer.work.weixin.qq.com/document/path/90256
func (r *Client) UploadImg(filename string) (*UploadImgResponse, error) {
@@ -28,7 +38,7 @@ func (r *Client) UploadImg(filename string) (*UploadImgResponse, error) {
return nil, err
}
var response []byte
if response, err = util.PostFile("media", filename, fmt.Sprintf(UploadImgURL, accessToken)); err != nil {
if response, err = util.PostFile("media", filename, fmt.Sprintf(uploadImgURL, accessToken)); err != nil {
return nil, err
}
result := &UploadImgResponse{}
@@ -37,3 +47,25 @@ func (r *Client) UploadImg(filename string) (*UploadImgResponse, error) {
}
return result, nil
}
// UploadTempFile 上传临时素材
// @see https://developer.work.weixin.qq.com/document/path/90253
// @mediaType 媒体文件类型分别有图片image、语音voice、视频video普通文件file
func (r *Client) UploadTempFile(filename string, mediaType string) (*UploadTempFileResponse, 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(uploadTempFile, accessToken, mediaType)); err != nil {
return nil, err
}
result := &UploadTempFileResponse{}
if err = util.DecodeWithError(response, result, "UploadTempFile"); err != nil {
return nil, err
}
return result, nil
}

16
work/message/client.go Normal file
View File

@@ -0,0 +1,16 @@
// Package message 消息推送,实现企业微信消息推送相关接口https://developer.work.weixin.qq.com/document/path/90235
package message
import (
"github.com/silenceper/wechat/v2/work/context"
)
// Client 消息推送接口实例
type Client struct {
*context.Context
}
// NewClient 初始化实例
func NewClient(ctx *context.Context) *Client {
return &Client{ctx}
}

132
work/message/message.go Normal file
View File

@@ -0,0 +1,132 @@
package message
import (
"encoding/json"
"fmt"
"github.com/silenceper/wechat/v2/util"
)
const (
// 发送应用消息的接口地址
sendURL = "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=%s"
)
type (
// SendRequestCommon 发送应用消息请求公共参数
SendRequestCommon struct {
// 指定接收消息的成员成员ID列表多个接收者用|分隔最多支持1000个。 特殊情况:指定为"@all",则向该企业应用的全部成员发送
ToUser string `json:"touser"`
// 指定接收消息的部门部门ID列表多个接收者用|分隔最多支持100个。 当touser为"@all"时忽略本参数
ToParty string `json:"toparty"`
// 指定接收消息的标签标签ID列表多个接收者用|分隔最多支持100个。 当touser为"@all"时忽略本参数
ToTag string `json:"totag"`
// 消息类型此时固定为text
MsgType string `json:"msgtype"`
// 企业应用的id整型。企业内部开发可在应用的设置页面查看第三方服务商可通过接口 获取企业授权信息 获取该参数值
AgentID string `json:"agentid"`
// 表示是否是保密消息0表示可对外分享1表示不能分享且内容显示水印默认为0
Safe int `json:"safe"`
// 表示是否开启id转译0表示否1表示是默认0。仅第三方应用需要用到企业自建应用可以忽略。
EnableIDTrans int `json:"enable_id_trans"`
// 表示是否开启重复消息检查0表示否1表示是默认0
EnableDuplicateCheck int `json:"enable_duplicate_check"`
// 表示是否重复消息检查的时间间隔默认1800s最大不超过4小时
DuplicateCheckInterval int `json:"duplicate_check_interval"`
}
// SendResponse 发送应用消息响应参数
SendResponse struct {
util.CommonError
InvalidUser string `json:"invaliduser"` // 不合法的userid不区分大小写统一转为小写
InvalidParty string `json:"invalidparty"` // 不合法的partyid
InvalidTag string `json:"invalidtag"` // 不合法的标签id
UnlicensedUser string `json:"unlicenseduser"` // 没有基础接口许可(包含已过期)的userid
MsgID string `json:"msgid"` // 消息id
ResponseCode string `json:"response_code"`
}
// SendTextRequest 发送文本消息的请求
SendTextRequest struct {
*SendRequestCommon
Text TextField `json:"text"`
}
// TextField 文本消息参数
TextField struct {
// 消息内容最长不超过2048个字节超过将截断支持id转译
Content string `json:"content"`
}
// SendImageRequest 发送图片消息的请求
SendImageRequest struct {
*SendRequestCommon
Image ImageField `json:"image"`
}
// ImageField 图片消息参数
ImageField struct {
// 图片媒体文件id可以调用上传临时素材接口获取
MediaID string `json:"media_id"`
}
// SendVoiceRequest 发送语音消息的请求
SendVoiceRequest struct {
*SendRequestCommon
Voice VoiceField `json:"voice"`
}
// VoiceField 语音消息参数
VoiceField struct {
// 语音文件id可以调用上传临时素材接口获取
MediaID string `json:"media_id"`
}
)
// Send 发送应用消息
// @desc 实现企业微信发送应用消息接口https://developer.work.weixin.qq.com/document/path/90236
func (r *Client) Send(apiName string, request interface{}) (*SendResponse, error) {
// 获取accessToken
accessToken, err := r.GetAccessToken()
if err != nil {
return nil, err
}
// 请求参数转 JSON 格式
jsonData, err := json.Marshal(request)
if err != nil {
return nil, err
}
// 发起http请求
response, err := util.HTTPPost(fmt.Sprintf(sendURL, accessToken), string(jsonData))
if err != nil {
return nil, err
}
// 按照结构体解析返回值
result := &SendResponse{}
if err = util.DecodeWithError(response, result, apiName); err != nil {
return nil, err
}
// 返回数据
return result, nil
}
// SendText 发送文本消息
func (r *Client) SendText(request SendTextRequest) (*SendResponse, error) {
// 发送文本消息MsgType参数固定为text
request.MsgType = "text"
return r.Send("MessageSendText", request)
}
// SendImage 发送图片消息
func (r *Client) SendImage(request SendImageRequest) (*SendResponse, error) {
// 发送图片消息MsgType参数固定为image
request.MsgType = "image"
return r.Send("MessageSendImage", request)
}
// SendVoice 发送语音消息
func (r *Client) SendVoice(request SendVoiceRequest) (*SendResponse, error) {
// 发送语音消息MsgType参数固定为voice
request.MsgType = "voice"
return r.Send("MessageSendVoice", request)
}
// 以上实现了部分常用消息推送SendText 发送文本消息、SendImage 发送图片消息、SendVoice 发送语音消息,
// 如需扩展其他消息类型,建议按照以上格式,扩展对应消息类型的参数即可
// 也可以直接使用Send方法按照企业微信消息推送的接口文档传对应消息类型的参数来使用

View File

@@ -150,7 +150,7 @@ type TodoMessage struct {
BaseMessage
Todo struct {
Title string `json:"title,omitempty"` // 代办的来源文本
Content string `json:"content,omitempty"` // 代办的具体内容
Content string `json:"content,omitempty"` // 代办的具体内容
} `json:"todo,omitempty"`
}
@@ -266,10 +266,10 @@ type VoipDocShareMessage struct {
type ExternalRedPacketMessage struct {
BaseMessage
RedPacket struct {
Type int32 `json:"type,omitempty"` // 红包消息类型。1 普通红包、2 拼手气群红包。Uint32类型
Wish int32 `json:"wish,omitempty"` // 红包祝福语。String类型
TotalCnt int32 `json:"totalcnt,omitempty"` // 红包总个数。Uint32类型
TotalAmount int32 `json:"totalamount,omitempty"` // 红包消息类型。1 普通红包、2 拼手气群红包。Uint32类型
Type uint32 `json:"type,omitempty"` // 红包消息类型。1 普通红包、2 拼手气群红包。Uint32类型
Wish string `json:"wish,omitempty"` // 红包祝福语。String类型
TotalCnt uint32 `json:"totalcnt,omitempty"` // 红包总个数。Uint32类型
TotalAmount uint32 `json:"totalamount,omitempty"` // 红包总金额。Uint32类型,单位为分。
} `json:"redpacket,omitempty"`
}
@@ -277,9 +277,9 @@ type ExternalRedPacketMessage struct {
type SphFeedMessage struct {
BaseMessage
SphFeed struct {
FeedType string `json:"feed_type,omitempty"` // 视频号消息类型
SphName string `json:"sph_name,omitempty"` // 视频号账号名称
FeedDesc uint64 `json:"feed_desc,omitempty"` // 视频号账号名称
FeedType uint32 `json:"feed_type,omitempty"` // 视频号消息类型。2 图片、4 视频、9 直播。Uint32类型
SphName string `json:"sph_name,omitempty"` // 视频号账号名称。String类型
FeedDesc string `json:"feed_desc,omitempty"` // 视频号消息描述。String类型
}
}

View File

@@ -8,15 +8,15 @@ import (
)
const (
// WebhookSendURL 机器人发送群组消息
WebhookSendURL = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=%s"
// 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 {
if data, err = util.PostJSON(fmt.Sprintf(webhookSendURL, webhookKey), options); err != nil {
return
}
if err = json.Unmarshal(data, &info); err != nil {

View File

@@ -8,6 +8,7 @@ import (
"github.com/silenceper/wechat/v2/work/externalcontact"
"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"
@@ -67,3 +68,8 @@ func (wk *Work) GetMaterial() *material.Client {
func (wk *Work) GetRobot() *robot.Client {
return robot.NewClient(wk.ctx)
}
// GetMessage get robot
func (wk *Work) GetMessage() *message.Client {
return message.NewClient(wk.ctx)
}