mirror of
https://github.com/silenceper/wechat.git
synced 2026-02-04 12:52:27 +08:00
Compare commits
24 Commits
v2.1.1-rc.
...
v2.1.4-rc.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a07c50fda7 | ||
|
|
eb3dbf1646 | ||
|
|
27e18b6958 | ||
|
|
252fc4838b | ||
|
|
fdaffb6aa2 | ||
|
|
108a65ecf3 | ||
|
|
cc9be30ed1 | ||
|
|
adf142dac2 | ||
|
|
ded5a10f9f | ||
|
|
e1307648fd | ||
|
|
c6325ace85 | ||
|
|
f2e7979a9f | ||
|
|
890e9fed43 | ||
|
|
3cbc95732a | ||
|
|
182339c00f | ||
|
|
e952b1d55a | ||
|
|
4b972c740f | ||
|
|
d5e7c8043e | ||
|
|
56350c3655 | ||
|
|
538c0b4e5f | ||
|
|
8fe9e6dcb6 | ||
|
|
8e81a416c5 | ||
|
|
1b5c5fba67 | ||
|
|
5c4b28acee |
1
.github/ISSUE_TEMPLATE.md
vendored
1
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,2 +1,3 @@
|
||||
## 问题及现象
|
||||
|
||||
<!-- 描述你的问题现象,报错**贴截图**粘贴或者贴具体信息,提供**必要的代码段**
|
||||
|
||||
13
.github/workflows/go.yml
vendored
13
.github/workflows/go.yml
vendored
@@ -10,13 +10,14 @@ jobs:
|
||||
golangci:
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.15.x]
|
||||
go-version: [1.15.x,1.16.x,1.17.x]
|
||||
name: golangci-lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-go@v2
|
||||
- uses: actions/checkout@v2
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v2
|
||||
uses: golangci/golangci-lint-action@v3.1.0
|
||||
with:
|
||||
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
|
||||
version: v1.31
|
||||
@@ -33,12 +34,18 @@ jobs:
|
||||
image: memcached
|
||||
ports:
|
||||
- 11211:11211
|
||||
|
||||
# strategy set
|
||||
strategy:
|
||||
matrix:
|
||||
go: ["1.15", "1.16", "1.17", "1.18"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Go 1.x
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ^1.13
|
||||
go-version: ${{ matrix.go }}
|
||||
id: go
|
||||
- name: Test
|
||||
run: go test -v -race ./...
|
||||
|
||||
@@ -36,8 +36,13 @@ linters:
|
||||
- whitespace
|
||||
|
||||
issues:
|
||||
# Excluding configuration per-path, per-linter, per-text and per-source
|
||||
include:
|
||||
- EXC0002 # disable excluding of issues about comments from golint
|
||||
exclude-rules:
|
||||
- linters:
|
||||
- stylecheck
|
||||
text: "ST1000:"
|
||||
# Excluding configuration per-path, per-linter, per-text and per-source
|
||||
- path: _test\.go
|
||||
linters:
|
||||
- gomnd
|
||||
@@ -52,10 +57,10 @@ linters-settings:
|
||||
lines: 66
|
||||
statements: 40
|
||||
|
||||
issues:
|
||||
include:
|
||||
- EXC0002 # disable excluding of issues about comments from golint
|
||||
exclude-rules:
|
||||
- linters:
|
||||
- stylecheck
|
||||
text: "ST1000:"
|
||||
#issues:
|
||||
# include:
|
||||
# - EXC0002 # disable excluding of issues about comments from golint
|
||||
# exclude-rules:
|
||||
# - linters:
|
||||
# - stylecheck
|
||||
# text: "ST1000:"
|
||||
|
||||
22
README.md
22
README.md
@@ -1,22 +1,23 @@
|
||||
# WeChat SDK for Go
|
||||
|
||||

|
||||
[](https://goreportcard.com/report/github.com/silenceper/wechat)
|
||||
[](https://pkg.go.dev/github.com/silenceper/wechat/v2?tab=doc)
|
||||

|
||||
|
||||
使用Golang开发的微信SDK,简单、易用。
|
||||
>注意:当前版本为v2版本,v1版本已废弃
|
||||
|
||||
> 注意:当前版本为v2版本,v1版本已废弃
|
||||
|
||||
## 文档 && 例子
|
||||
|
||||
[API列表](https://github.com/silenceper/wechat/tree/v2/doc/api)
|
||||
|
||||
[Wechat SDK 2.0 文档](https://silenceper.com/wechat)
|
||||
|
||||
[Wechat SDK 2.0 例子](https://github.com/gowechat/example)
|
||||
|
||||
|
||||
## 快速开始
|
||||
|
||||
```
|
||||
import "github.com/silenceper/wechat/v2"
|
||||
```
|
||||
@@ -24,40 +25,41 @@ import "github.com/silenceper/wechat/v2"
|
||||
以下是一个微信公众号处理消息接收以及回复的例子:
|
||||
|
||||
```go
|
||||
//使用memcache保存access_token,也可选择redis或自定义cache
|
||||
// 使用memcache保存access_token,也可选择redis或自定义cache
|
||||
wc := wechat.NewWechat()
|
||||
memory := cache.NewMemory()
|
||||
cfg := &offConfig.Config{
|
||||
AppID: "xxx",
|
||||
AppSecret: "xxx",
|
||||
Token: "xxx",
|
||||
//EncodingAESKey: "xxxx",
|
||||
// EncodingAESKey: "xxxx",
|
||||
Cache: memory,
|
||||
}
|
||||
officialAccount := wc.GetOfficialAccount(cfg)
|
||||
|
||||
// 传入request和responseWriter
|
||||
server := officialAccount.GetServer(req, rw)
|
||||
//设置接收消息的处理方法
|
||||
// 设置接收消息的处理方法
|
||||
server.SetMessageHandler(func(msg *message.MixMessage) *message.Reply {
|
||||
|
||||
//回复消息:演示回复用户发送的消息
|
||||
// 回复消息:演示回复用户发送的消息
|
||||
text := message.NewText(msg.Content)
|
||||
return &message.Reply{MsgType: message.MsgTypeText, MsgData: text}
|
||||
})
|
||||
|
||||
//处理消息接收以及回复
|
||||
// 处理消息接收以及回复
|
||||
err := server.Serve()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
//发送回复的消息
|
||||
// 发送回复的消息
|
||||
server.Send()
|
||||
|
||||
```
|
||||
|
||||
## 目录说明
|
||||
|
||||
- officialaccount: 微信公众号API
|
||||
- miniprogram: 小程序API
|
||||
- minigame:小游戏API
|
||||
@@ -68,11 +70,13 @@ server.Send()
|
||||
- doc: api文档
|
||||
|
||||
## 贡献
|
||||
|
||||
- 在[API列表](https://github.com/silenceper/wechat/tree/v2/doc/api)中查看哪些API未实现
|
||||
- 提交issue,描述需要贡献的内容
|
||||
- 完成更改后,提交PR
|
||||
|
||||
## 公众号
|
||||
|
||||

|
||||
|
||||
## License
|
||||
|
||||
94
cache/redis.go
vendored
94
cache/redis.go
vendored
@@ -1,15 +1,16 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/gomodule/redigo/redis"
|
||||
"github.com/go-redis/redis/v8"
|
||||
)
|
||||
|
||||
// Redis .redis cache
|
||||
type Redis struct {
|
||||
conn *redis.Pool
|
||||
ctx context.Context
|
||||
conn redis.UniversalClient
|
||||
}
|
||||
|
||||
// RedisOpts redis 连接属性
|
||||
@@ -23,90 +24,49 @@ type RedisOpts struct {
|
||||
}
|
||||
|
||||
// NewRedis 实例化
|
||||
func NewRedis(opts *RedisOpts, dialOpts ...redis.DialOption) *Redis {
|
||||
pool := &redis.Pool{
|
||||
MaxActive: opts.MaxActive,
|
||||
MaxIdle: opts.MaxIdle,
|
||||
IdleTimeout: time.Second * time.Duration(opts.IdleTimeout),
|
||||
Dial: func() (redis.Conn, error) {
|
||||
dialOpts = append(dialOpts, []redis.DialOption{
|
||||
redis.DialDatabase(opts.Database),
|
||||
redis.DialPassword(opts.Password),
|
||||
}...)
|
||||
return redis.Dial("tcp", opts.Host, dialOpts...)
|
||||
},
|
||||
TestOnBorrow: func(conn redis.Conn, t time.Time) error {
|
||||
if time.Since(t) < time.Minute {
|
||||
return nil
|
||||
}
|
||||
_, err := conn.Do("PING")
|
||||
return err
|
||||
},
|
||||
}
|
||||
return &Redis{pool}
|
||||
}
|
||||
|
||||
// SetRedisPool 设置redis连接池
|
||||
func (r *Redis) SetRedisPool(pool *redis.Pool) {
|
||||
r.conn = pool
|
||||
func NewRedis(ctx context.Context, opts *RedisOpts) *Redis {
|
||||
conn := redis.NewUniversalClient(&redis.UniversalOptions{
|
||||
Addrs: []string{opts.Host},
|
||||
DB: opts.Database,
|
||||
Password: opts.Password,
|
||||
IdleTimeout: time.Second * time.Duration(opts.IdleTimeout),
|
||||
MinIdleConns: opts.MaxIdle,
|
||||
})
|
||||
return &Redis{ctx: ctx, conn: conn}
|
||||
}
|
||||
|
||||
// SetConn 设置conn
|
||||
func (r *Redis) SetConn(conn *redis.Pool) {
|
||||
func (r *Redis) SetConn(conn redis.UniversalClient) {
|
||||
r.conn = conn
|
||||
}
|
||||
|
||||
// SetRedisCtx 设置redis ctx 参数
|
||||
func (r *Redis) SetRedisCtx(ctx context.Context) {
|
||||
r.ctx = ctx
|
||||
}
|
||||
|
||||
// Get 获取一个值
|
||||
func (r *Redis) Get(key string) interface{} {
|
||||
conn := r.conn.Get()
|
||||
defer conn.Close()
|
||||
|
||||
var data []byte
|
||||
var err error
|
||||
if data, err = redis.Bytes(conn.Do("GET", key)); err != nil {
|
||||
result, err := r.conn.Do(r.ctx, "GET", key).Result()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
var reply interface{}
|
||||
if err = json.Unmarshal(data, &reply); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return reply
|
||||
return result
|
||||
}
|
||||
|
||||
// Set 设置一个值
|
||||
func (r *Redis) Set(key string, val interface{}, timeout time.Duration) (err error) {
|
||||
conn := r.conn.Get()
|
||||
defer conn.Close()
|
||||
|
||||
var data []byte
|
||||
if data, err = json.Marshal(val); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = conn.Do("SETEX", key, int64(timeout/time.Second), data)
|
||||
|
||||
return
|
||||
func (r *Redis) Set(key string, val interface{}, timeout time.Duration) error {
|
||||
return r.conn.SetEX(r.ctx, key, val, timeout).Err()
|
||||
}
|
||||
|
||||
// IsExist 判断key是否存在
|
||||
func (r *Redis) IsExist(key string) bool {
|
||||
conn := r.conn.Get()
|
||||
defer conn.Close()
|
||||
result, _ := r.conn.Exists(r.ctx, key).Result()
|
||||
|
||||
a, _ := conn.Do("EXISTS", key)
|
||||
i := a.(int64)
|
||||
return i > 0
|
||||
return result > 0
|
||||
}
|
||||
|
||||
// Delete 删除
|
||||
func (r *Redis) Delete(key string) error {
|
||||
conn := r.conn.Get()
|
||||
defer conn.Close()
|
||||
|
||||
if _, err := conn.Do("DEL", key); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return r.conn.Del(r.ctx, key).Err()
|
||||
}
|
||||
|
||||
29
cache/redis_test.go
vendored
29
cache/redis_test.go
vendored
@@ -1,33 +1,40 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestRedis(t *testing.T) {
|
||||
opts := &RedisOpts{
|
||||
Host: "127.0.0.1:6379",
|
||||
}
|
||||
redis := NewRedis(opts)
|
||||
var (
|
||||
timeoutDuration = time.Second
|
||||
ctx = context.Background()
|
||||
opts = &RedisOpts{
|
||||
Host: "127.0.0.1:6379",
|
||||
}
|
||||
redis = NewRedis(ctx, opts)
|
||||
err error
|
||||
val = "silenceper"
|
||||
key = "username"
|
||||
)
|
||||
redis.SetConn(redis.conn)
|
||||
var err error
|
||||
timeoutDuration := 1 * time.Second
|
||||
redis.SetRedisCtx(ctx)
|
||||
|
||||
if err = redis.Set("username", "silenceper", timeoutDuration); err != nil {
|
||||
if err = redis.Set(key, val, timeoutDuration); err != nil {
|
||||
t.Error("set Error", err)
|
||||
}
|
||||
|
||||
if !redis.IsExist("username") {
|
||||
if !redis.IsExist(key) {
|
||||
t.Error("IsExist Error")
|
||||
}
|
||||
|
||||
name := redis.Get("username").(string)
|
||||
if name != "silenceper" {
|
||||
name := redis.Get(key).(string)
|
||||
if name != val {
|
||||
t.Error("get Error")
|
||||
}
|
||||
|
||||
if err = redis.Delete("username"); err != nil {
|
||||
if err = redis.Delete(key); err != nil {
|
||||
t.Errorf("delete Error , err=%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
# API 文档
|
||||
|
||||
已完成以及未完成API列表汇总
|
||||
|
||||
如果有兴趣参与贡献,可以在具体的API表格后面标识自己为贡献者以及完成时间,例如:
|
||||
|
||||
|
||||
| 名称 | 请求方式 | URL | 是否已实现 | 使用方法 |贡献者|完成时间|
|
||||
| :---------------------: | -------- | :------------------------- | ---------- | -------- |-------- |-------- |
|
||||
| 获取公众号类目 | GET | /wxaapi/newtmpl/getcategory | NO | |silenceper| 2021-12-20|
|
||||
|
||||
|
||||
- [微信公众号](./officialaccount.md)
|
||||
- [小程序](./miniprogram.md)
|
||||
- [小游戏](./minigame.md)
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
# 智能对话
|
||||
|
||||
TODO
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
# 小游戏
|
||||
|
||||
TODO
|
||||
@@ -1,2 +1,50 @@
|
||||
# 小程序
|
||||
TODO
|
||||
|
||||
## 基础接口
|
||||
|
||||
TODO
|
||||
|
||||
## 内容安全
|
||||
|
||||
[官方文档](https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/sec-check/security.mediaCheckAsync.html)
|
||||
|
||||
| 名称 | 请求方式 | URL | 是否已实现 | 使用方法 |
|
||||
| :-------------------------------: | -------- | :------------------------------- | ---------- | -------------------------------------- |
|
||||
| 异步校验图片/音频 <sub>v1.0</sub> | POST | /wxa/media_check_async | YES | (security *Security) MediaCheckAsyncV1 |
|
||||
| 同步校验一张图片 <sub>v1.0</sub> | POST | /wxa/img_sec_check | YES | (security *Security) ImageCheckV1 |
|
||||
| 异步校验图片/音频 | POST | /wxa/media_check_async?version=2 | YES | (security *Security) MediaCheckAsync |
|
||||
| 同步检查一段文本 <sub>v1.0</sub> | POST | /wxa/msg_sec_check | YES | (security *Security) MsgCheckV1 |
|
||||
| 同步检查一段文本 | POST | /wxa/msg_sec_check?version=2 | YES | (security *Security) MsgCheck |
|
||||
|
||||
|
||||
## OCR
|
||||
|
||||
[官方文档](https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/ocr/ocr.bankcard.html)
|
||||
|
||||
| 名称 | 请求方式 | URL | 是否已实现 | 使用方法 |
|
||||
| :------------: | -------- | :--------------------- | ---------- | -------- |
|
||||
| 银行卡识别 | POST | /cv/ocr/bankcard | | |
|
||||
| 营业执照识别 | POST | /cv/ocr/bizlicense | | |
|
||||
| 驾驶证识别 | POST | /cv/ocr/drivinglicense | | |
|
||||
| 身份证识别 | POST | /cv/ocr/idcard | | |
|
||||
| 通用印刷体识别 | POST | /cv/ocr/comm | | |
|
||||
| 行驶证识别 | POST | /cv/ocr/driving | | |
|
||||
|
||||
|
||||
## 手机号
|
||||
|
||||
[官方文档](https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/phonenumber/phonenumber.getPhoneNumber.html)
|
||||
|
||||
| 名称 | 请求方式 | URL | 是否已实现 | 使用方法 |
|
||||
| :----------------: | -------- | :------------------------------- | ---------- | ----------------------------------- |
|
||||
| code换取用户手机号 | POST | /wxa/business/getuserphonenumber | YES | (business *Business) GetPhoneNumber |
|
||||
|
||||
|
||||
## 安全风控
|
||||
|
||||
[官方文档](https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/safety-control-capability/riskControl.getUserRiskRank.html)
|
||||
|
||||
| 名称 | 请求方式 | URL | 是否已实现 | 使用方法 |
|
||||
| :----------------: | -------- | :------------------- | ---------- | ------------------------------------------ |
|
||||
| 获取用户的安全等级 | POST | /wxa/getuserriskrank | YES | (riskControl *RiskControl) GetUserRiskRank |
|
||||
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
| -------------------- | -------- | -------------------------------------- | ---------- | ----------------------- |
|
||||
| 选用模板 | POST | /wxaapi/newtmpl/addtemplate | YES | (tpl *Subscribe) Add |
|
||||
| 删除模板 | POST | /wxaapi/newtmpl/deltemplate | YES | (tpl *Subscribe) Delete |
|
||||
| 获取公众号类目 | GET | /wxaapi/newtmpl/getcategory | NO | |
|
||||
| 获取模板中的关键词 | GET | /wxaapi/newtmpl/getpubtemplatekeywords | NO | |
|
||||
| 获取类目下的公共模板 | GET | /wxaapi/newtmpl/getpubtemplatetitles | NO | |
|
||||
| 获取公众号类目 | GET | /wxaapi/newtmpl/getcategory | YES | (tpl *Subscribe) GetCategory |
|
||||
| 获取模板中的关键词 | GET | /wxaapi/newtmpl/getpubtemplatekeywords | YES | (tpl *Subscribe) GetPubTplKeyWordsByID |
|
||||
| 获取类目下的公共模板 | GET | /wxaapi/newtmpl/getpubtemplatetitles | YES | (tpl *Subscribe) GetPublicTemplateTitleList |
|
||||
| 获取私有模板列表 | GET | /wxaapi/newtmpl/gettemplate | YES | (tpl *Subscribe) List() |
|
||||
| 发送订阅通知 | POST | /cgi-bin/message/subscribe/bizsend | YES | (tpl *Subscribe) Send |
|
||||
|
||||
@@ -132,6 +132,8 @@
|
||||
|
||||
#### 群发任务管理
|
||||
|
||||
[官方文档](https://developers.weixin.qq.com/doc/offiaccount/Shopping_Guide/task-account/shopping-guide.addGuideMassendJob.html)
|
||||
|
||||
| 名称 | 请求方式 | URL | 是否已实现 | 使用方法 |
|
||||
| -------------------- | -------- | ------------------------------------- | ---------- | -------- |
|
||||
| 添加群发任务 | POST | /cgi-bin/guide/addguidemassendjob | NO | |
|
||||
@@ -156,6 +158,43 @@
|
||||
|
||||
## 素材管理
|
||||
|
||||
## 草稿箱
|
||||
|
||||
[官方文档](https://developers.weixin.qq.com/doc/offiaccount/Draft_Box/Add_draft.html)
|
||||
|
||||
| 名称 | 请求方式 | URL | 是否已实现 | 使用方法 |
|
||||
| -------------------------- | -------- | ------------------------------------------------------------ | ---------- | ---------------------------- |
|
||||
| 新建草稿 | POST | /cgi-bin/draft/add | YES | (draft *Draft) AddDraft |
|
||||
| 获取草稿 | POST | /cgi-bin/draft/get | YES | (draft *Draft) GetDraft |
|
||||
| 删除草稿 | POST | /cgi-bin/draft/delete | YES | (draft *Draft) DeleteDraft |
|
||||
| 修改草稿 | POST | /cgi-bin/draft/update | YES | (draft *Draft) UpdateDraft |
|
||||
| 获取草稿总数 | GET | /cgi-bin/draft/count | YES | (draft *Draft) CountDraft |
|
||||
| 获取草稿列表 | POST | /cgi-bin/draft/batchget | YES | (draft *Draft) PaginateDraft |
|
||||
| MP端开关(仅内测期间使用) | POST | /cgi-bin/draft/switch<br />/cgi-bin/draft/switch?checkonly=1 | NO | |
|
||||
|
||||
|
||||
|
||||
## 发布能力
|
||||
|
||||
[官方文档](https://developers.weixin.qq.com/doc/offiaccount/Publish/Publish.html)
|
||||
|
||||
说明:「发表记录」包括群发和发布。
|
||||
|
||||
注意:该接口,只能处理 "发布" 相关的信息,无法操作和获取 "群发" 相关内容!
|
||||
|
||||
- 群发:主动推送给粉丝,历史消息可看,被搜一搜收录,可以限定部分的粉丝接收到。
|
||||
- 发布:不会主动推给粉丝,历史消息列表看不到,但是是公开给所有人的文章。也不会占用群发的次数。每天可以发布多篇内容。可以用于自动回复、自定义菜单、页面模板和话题中,发布成功时会生成一个永久链接。
|
||||
|
||||
| 名称 | 请求方式 | URL | 是否已实现 | 使用方法 |
|
||||
| ------------------------------ | -------- | ------------------------------- | ---------- | --------------------------------------- |
|
||||
| 发布接口 | POST | /cgi-bin/freepublish/submit | YES | (freePublish *FreePublish) Publish |
|
||||
| 发布状态轮询接口 | POST | /cgi-bin/freepublish/get | YES | (freePublish *FreePublish) SelectStatus |
|
||||
| 事件推送发布结果 | | | YES | EventPublishJobFinish |
|
||||
| 删除发布 | POST | /cgi-bin/freepublish/delete | YES | (freePublish *FreePublish) Delete |
|
||||
| 通过 article_id 获取已发布文章 | POST | /cgi-bin/freepublish/getarticle | YES | (freePublish *FreePublish) First |
|
||||
| 获取成功发布列表 | POST | /cgi-bin/freepublish/batchget | YES | (freePublish *FreePublish) Paginate |
|
||||
|
||||
|
||||
## 图文消息留言管理
|
||||
|
||||
## 用户管理
|
||||
|
||||
@@ -59,6 +59,15 @@ host: https://qyapi.weixin.qq.com/
|
||||
| 获取客户基础信息 | POST | /cgi-bin/kf/customer/batchget | YES | (r *Client) CustomerBatchGet | NICEXAI |
|
||||
| 获取视频号绑定状态 | GET | /cgi-bin/kf/get_corp_qualification | YES | (r *Client) GetCorpQualification | NICEXAI |
|
||||
|
||||
### 客户联系
|
||||
[官方文档](https://developer.work.weixin.qq.com/document/path/92132/92133)
|
||||
|
||||
| 名称 | 请求方式 | URL | 是否已实现 | 使用方法 | 贡献者 |
|
||||
|:---------------:| -------- | :---------------------------------------| ---------- | ------------------------------- |----------|
|
||||
| 获取「联系客户统计」数据 | POST | /cgi-bin/externalcontact/get_user_behavior_data | YES | (r *Client) GetUserBehaviorData | MARKWANG |
|
||||
| 获取「群聊数据统计」数据 (按群主聚合的方式) | POST | /cgi-bin/externalcontact/groupchat/statistic | YES | (r *Client) GetGroupChatStat | MARKWANG |
|
||||
| 获取「群聊数据统计」数据 (按自然日聚合的方式) | POST | /cgi-bin/externalcontact/groupchat/statistic_group_by_day | YES | (r *Client) GetGroupChatStatByDay | MARKWANG |
|
||||
|
||||
## 应用管理
|
||||
|
||||
TODO
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
# 微信支付
|
||||
|
||||
TODO
|
||||
|
||||
21
go.mod
21
go.mod
@@ -1,18 +1,15 @@
|
||||
module github.com/silenceper/wechat/v2
|
||||
|
||||
go 1.14
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b
|
||||
github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d
|
||||
github.com/fatih/structs v1.1.0
|
||||
github.com/gomodule/redigo v1.8.5
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/spf13/cast v1.3.1
|
||||
github.com/stretchr/testify v1.7.0
|
||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83
|
||||
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
|
||||
gopkg.in/h2non/gock.v1 v1.0.15
|
||||
github.com/go-redis/redis/v8 v8.11.5
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/spf13/cast v1.4.1
|
||||
github.com/stretchr/testify v1.7.1
|
||||
github.com/tidwall/gjson v1.14.1
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d
|
||||
gopkg.in/h2non/gock.v1 v1.1.2
|
||||
)
|
||||
|
||||
148
go.sum
148
go.sum
@@ -1,49 +1,145 @@
|
||||
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b h1:L/QXpzIa3pOvUGt1D1lA5KjYhPBAN/3iWdP7xeFS9F0=
|
||||
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
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=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/gomodule/redigo v1.8.5 h1:nRAxCa+SVsyjSBrtZmG/cqb6VbTmuRzpg/PoTFlpumc=
|
||||
github.com/gomodule/redigo v1.8.5/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
|
||||
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=
|
||||
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||
github.com/onsi/ginkgo/v2 v2.0.0 h1:CcuG/HvWNkkaqCUpJifQY8z7qEMBJya6aLPx6ftGyjQ=
|
||||
github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
|
||||
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
|
||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
|
||||
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/tidwall/gjson v1.14.1 h1:iymTbGkQBhveq21bEvAQ81I0LEBork8BFe1CUZXdyuo=
|
||||
github.com/tidwall/gjson v1.14.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
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=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g=
|
||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
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=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2 h1:46ULzRKLh1CwgRq2dC5SlBzEqqNCi8rreOZnNrbqcIY=
|
||||
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
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=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/h2non/gock.v1 v1.0.15 h1:SzLqcIlb/fDfg7UvukMpNcWsu7sI5tWwL+KCATZqks0=
|
||||
gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY=
|
||||
gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
[官方文档](https://developers.weixin.qq.com/minigame/dev/api-backend/)
|
||||
|
||||
## 快速入门
|
||||
|
||||
```go
|
||||
|
||||
```
|
||||
@@ -2,11 +2,12 @@
|
||||
|
||||
[官方文档](https://developers.weixin.qq.com/miniprogram/dev/framework/)
|
||||
|
||||
|
||||
## 包说明
|
||||
|
||||
- analysis 数据分析相关API
|
||||
|
||||
## 快速入门
|
||||
|
||||
```go
|
||||
wc := wechat.NewWechat()
|
||||
memory := cache.NewMemory()
|
||||
|
||||
@@ -84,7 +84,9 @@ func (auth *Auth) CheckEncryptedDataContext(ctx context2.Context, encryptedMsgHa
|
||||
if at, err = auth.GetAccessToken(); err != nil {
|
||||
return
|
||||
}
|
||||
if response, err = util.HTTPPostContext(ctx, fmt.Sprintf(checkEncryptedDataURL, at), "encrypted_msg_hash="+encryptedMsgHash); err != nil {
|
||||
|
||||
// 由于GetPhoneNumberContext需要传入JSON,所以HTTPPostContext入参改为[]byte
|
||||
if response, err = util.HTTPPostContext(ctx, fmt.Sprintf(checkEncryptedDataURL, at), []byte("encrypted_msg_hash="+encryptedMsgHash), nil); err != nil {
|
||||
return
|
||||
}
|
||||
if err = util.DecodeWithError(response, &result, "CheckEncryptedDataAuth"); err != nil {
|
||||
@@ -111,23 +113,38 @@ type PhoneInfo struct {
|
||||
} `json:"watermark"` // 数据水印
|
||||
}
|
||||
|
||||
// GetPhoneNumber 小程序通过code获取用户手机号
|
||||
func (auth *Auth) GetPhoneNumber(code string) (result GetPhoneNumberResponse, err error) {
|
||||
// GetPhoneNumberContext 小程序通过code获取用户手机号
|
||||
func (auth *Auth) GetPhoneNumberContext(ctx context2.Context, code string) (*GetPhoneNumberResponse, error) {
|
||||
var response []byte
|
||||
var (
|
||||
at string
|
||||
at string
|
||||
err error
|
||||
)
|
||||
if at, err = auth.GetAccessToken(); err != nil {
|
||||
return
|
||||
return nil, err
|
||||
}
|
||||
body := map[string]interface{}{
|
||||
"code": code,
|
||||
}
|
||||
if response, err = util.PostJSON(fmt.Sprintf(getPhoneNumber, at), body); err != nil {
|
||||
return
|
||||
|
||||
bodyBytes, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
header := map[string]string{"Content-Type": "application/json;charset=utf-8"}
|
||||
if response, err = util.HTTPPostContext(ctx, fmt.Sprintf(getPhoneNumber, at), bodyBytes, header); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result GetPhoneNumberResponse
|
||||
if err = util.DecodeWithError(response, &result, "phonenumber.getPhoneNumber"); err != nil {
|
||||
return
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// GetPhoneNumber 小程序通过code获取用户手机号
|
||||
func (auth *Auth) GetPhoneNumber(code string) (*GetPhoneNumberResponse, error) {
|
||||
return auth.GetPhoneNumberContext(context2.Background(), code)
|
||||
}
|
||||
|
||||
13
miniprogram/business/business.go
Normal file
13
miniprogram/business/business.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package business
|
||||
|
||||
import "github.com/silenceper/wechat/v2/miniprogram/context"
|
||||
|
||||
// Business 业务
|
||||
type Business struct {
|
||||
*context.Context
|
||||
}
|
||||
|
||||
// NewBusiness init
|
||||
func NewBusiness(ctx *context.Context) *Business {
|
||||
return &Business{ctx}
|
||||
}
|
||||
54
miniprogram/business/phone_number.go
Normal file
54
miniprogram/business/phone_number.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package business
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/silenceper/wechat/v2/util"
|
||||
)
|
||||
|
||||
const (
|
||||
getPhoneNumberURL = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=%s"
|
||||
)
|
||||
|
||||
// GetPhoneNumberRequest 获取手机号请求
|
||||
type GetPhoneNumberRequest struct {
|
||||
Code string `json:"code"` // 手机号获取凭证
|
||||
}
|
||||
|
||||
// PhoneInfo 手机号信息
|
||||
type PhoneInfo struct {
|
||||
PhoneNumber string `json:"phoneNumber"` // 用户绑定的手机号(国外手机号会有区号)
|
||||
PurePhoneNumber string `json:"purePhoneNumber"` // 没有区号的手机号
|
||||
CountryCode string `json:"countryCode"` // 区号
|
||||
Watermark struct {
|
||||
AppID string `json:"appid"` // 小程序appid
|
||||
Timestamp int64 `json:"timestamp"` // 用户获取手机号操作的时间戳
|
||||
} `json:"watermark"`
|
||||
}
|
||||
|
||||
// GetPhoneNumber code换取用户手机号。 每个code只能使用一次,code的有效期为5min
|
||||
func (business *Business) GetPhoneNumber(in *GetPhoneNumberRequest) (info PhoneInfo, err error) {
|
||||
accessToken, err := business.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf(getPhoneNumberURL, accessToken)
|
||||
response, err := util.PostJSON(uri, in)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 使用通用方法返回错误
|
||||
var resp struct {
|
||||
util.CommonError
|
||||
PhoneInfo PhoneInfo `json:"phone_info"`
|
||||
}
|
||||
err = util.DecodeWithError(response, &resp, "business.GetPhoneNumber")
|
||||
if nil != err {
|
||||
return
|
||||
}
|
||||
|
||||
info = resp.PhoneInfo
|
||||
return
|
||||
}
|
||||
@@ -8,6 +8,6 @@ import (
|
||||
// Config .config for 小程序
|
||||
type Config struct {
|
||||
AppID string `json:"app_id"` // appid
|
||||
AppSecret string `json:"app_secret"` // appsecret
|
||||
AppSecret string `json:"app_secret"` // appSecret
|
||||
Cache cache.Cache
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ func NewContent(ctx *context.Context) *Content {
|
||||
|
||||
// CheckText 检测文字
|
||||
// @text 需要检测的文字
|
||||
// Deprecated
|
||||
// 采用 security.MsgCheckV1 替代,返回值更加丰富
|
||||
func (content *Content) CheckText(text string) error {
|
||||
accessToken, err := content.GetAccessToken()
|
||||
if err != nil {
|
||||
@@ -44,6 +46,8 @@ func (content *Content) CheckText(text string) error {
|
||||
// CheckImage 检测图片
|
||||
// 所传参数为要检测的图片文件的绝对路径,图片格式支持PNG、JPEG、JPG、GIF, 像素不超过 750 x 1334,同时文件大小以不超过 300K 为宜,否则可能报错
|
||||
// @media 图片文件的绝对路径
|
||||
// Deprecated
|
||||
// 采用 security.ImageCheckV1 替代,返回值更加丰富
|
||||
func (content *Content) CheckImage(media string) error {
|
||||
accessToken, err := content.GetAccessToken()
|
||||
if err != nil {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"github.com/silenceper/wechat/v2/credential"
|
||||
"github.com/silenceper/wechat/v2/miniprogram/analysis"
|
||||
"github.com/silenceper/wechat/v2/miniprogram/auth"
|
||||
"github.com/silenceper/wechat/v2/miniprogram/business"
|
||||
"github.com/silenceper/wechat/v2/miniprogram/config"
|
||||
"github.com/silenceper/wechat/v2/miniprogram/content"
|
||||
"github.com/silenceper/wechat/v2/miniprogram/context"
|
||||
@@ -11,10 +12,13 @@ import (
|
||||
"github.com/silenceper/wechat/v2/miniprogram/message"
|
||||
"github.com/silenceper/wechat/v2/miniprogram/privacy"
|
||||
"github.com/silenceper/wechat/v2/miniprogram/qrcode"
|
||||
"github.com/silenceper/wechat/v2/miniprogram/riskcontrol"
|
||||
"github.com/silenceper/wechat/v2/miniprogram/security"
|
||||
"github.com/silenceper/wechat/v2/miniprogram/shortlink"
|
||||
"github.com/silenceper/wechat/v2/miniprogram/subscribe"
|
||||
"github.com/silenceper/wechat/v2/miniprogram/tcb"
|
||||
"github.com/silenceper/wechat/v2/miniprogram/urllink"
|
||||
"github.com/silenceper/wechat/v2/miniprogram/urlscheme"
|
||||
"github.com/silenceper/wechat/v2/miniprogram/werun"
|
||||
)
|
||||
|
||||
@@ -58,6 +62,11 @@ func (miniProgram *MiniProgram) GetAnalysis() *analysis.Analysis {
|
||||
return analysis.NewAnalysis(miniProgram.ctx)
|
||||
}
|
||||
|
||||
// GetBusiness 业务接口
|
||||
func (miniProgram *MiniProgram) GetBusiness() *business.Business {
|
||||
return business.NewBusiness(miniProgram.ctx)
|
||||
}
|
||||
|
||||
// GetPrivacy 小程序隐私协议相关API
|
||||
func (miniProgram *MiniProgram) GetPrivacy() *privacy.Privacy {
|
||||
return privacy.NewPrivacy(miniProgram.ctx)
|
||||
@@ -98,7 +107,22 @@ func (miniProgram *MiniProgram) GetURLLink() *urllink.URLLink {
|
||||
return urllink.NewURLLink(miniProgram.ctx)
|
||||
}
|
||||
|
||||
// GetRiskControl 安全风控接口
|
||||
func (miniProgram *MiniProgram) GetRiskControl() *riskcontrol.RiskControl {
|
||||
return riskcontrol.NewRiskControl(miniProgram.ctx)
|
||||
}
|
||||
|
||||
// GetSecurity 内容安全接口
|
||||
func (miniProgram *MiniProgram) GetSecurity() *security.Security {
|
||||
return security.NewSecurity(miniProgram.ctx)
|
||||
}
|
||||
|
||||
// GetShortLink 小程序短链接口
|
||||
func (miniProgram *MiniProgram) GetShortLink() *shortlink.ShortLink {
|
||||
return shortlink.NewShortLink(miniProgram.ctx)
|
||||
}
|
||||
|
||||
// GetSURLScheme 小程序URL Scheme接口
|
||||
func (miniProgram *MiniProgram) GetSURLScheme() *urlscheme.URLScheme {
|
||||
return urlscheme.NewURLScheme(miniProgram.ctx)
|
||||
}
|
||||
|
||||
@@ -40,17 +40,17 @@ type QRCoder struct {
|
||||
Page string `json:"page,omitempty"`
|
||||
// path 扫码进入的小程序页面路径
|
||||
Path string `json:"path,omitempty"`
|
||||
// checkPath 检查page 是否存在,为 true 时 page 必须是已经发布的小程序存在的页面(否则报错);为 false 时允许小程序未发布或者 page 不存在, 但page 有数量上限(60000个)请勿滥用
|
||||
CheckPath bool `json:"check_path,omitempty"`
|
||||
// checkPath 检查page 是否存在,为 true 时 page 必须是已经发布的小程序存在的页面(否则报错);为 false 时允许小程序未发布或者 page 不存在, 但page 有数量上限(60000个)请勿滥用,默认true
|
||||
CheckPath *bool `json:"check_path,omitempty"`
|
||||
// width 图片宽度
|
||||
Width int `json:"width,omitempty"`
|
||||
// scene 最大32个可见字符,只支持数字,大小写英文以及部分特殊字符:!#$&'()*+,/:;=?@-._~,其它字符请自行编码为合法字符(因不支持%,中文无法使用 urlencode 处理,请使用其他编码方式)
|
||||
Scene string `json:"scene,omitempty"`
|
||||
// autoColor 自动配置线条颜色,如果颜色依然是黑色,则说明不建议配置主色调
|
||||
// autoColor 自动配置线条颜色,如果颜色依然是黑色,则说明不建议配置主色调,默认false
|
||||
AutoColor bool `json:"auto_color,omitempty"`
|
||||
// lineColor AutoColor 为 false 时生效,使用 rgb 设置颜色 例如 {"r":"xxx","g":"xxx","b":"xxx"},十进制表示
|
||||
LineColor Color `json:"line_color,omitempty"`
|
||||
// isHyaline 是否需要透明底色
|
||||
LineColor *Color `json:"line_color,omitempty"`
|
||||
// isHyaline 是否需要透明底色,默认false
|
||||
IsHyaline bool `json:"is_hyaline,omitempty"`
|
||||
// envVersion 要打开的小程序版本。正式版为 "release",体验版为 "trial",开发版为 "develop"
|
||||
EnvVersion string `json:"env_version,omitempty"`
|
||||
|
||||
60
miniprogram/riskcontrol/riskcontrol.go
Normal file
60
miniprogram/riskcontrol/riskcontrol.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package riskcontrol
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/silenceper/wechat/v2/miniprogram/context"
|
||||
"github.com/silenceper/wechat/v2/util"
|
||||
)
|
||||
|
||||
const (
|
||||
getUserRiskRankURL = "https://api.weixin.qq.com/wxa/getuserriskrank?access_token=%s"
|
||||
)
|
||||
|
||||
// RiskControl 安全风控
|
||||
type RiskControl struct {
|
||||
*context.Context
|
||||
}
|
||||
|
||||
// NewRiskControl init
|
||||
func NewRiskControl(ctx *context.Context) *RiskControl {
|
||||
return &RiskControl{ctx}
|
||||
}
|
||||
|
||||
// UserRiskRankRequest 获取用户安全等级请求
|
||||
type UserRiskRankRequest struct {
|
||||
AppID string `json:"appid"` // 小程序 app id
|
||||
OpenID string `json:"openid"` // 用户的 openid
|
||||
Scene uint8 `json:"scene"` // 场景值,0:注册,1:营销作弊
|
||||
ClientIP string `json:"client_ip"` // 用户访问源ip
|
||||
|
||||
Mobile string `json:"mobile_no"` // 用户手机号
|
||||
Email string `json:"email_address"` // 用户邮箱地址
|
||||
ExtendedInfo string `json:"extended_info"` // 额外补充信息
|
||||
IsTest bool `json:"is_test"` // false:正式调用,true:测试调用
|
||||
}
|
||||
|
||||
// UserRiskRank 用户安全等级
|
||||
type UserRiskRank struct {
|
||||
util.CommonError
|
||||
UnionID int64 `json:"union_id"` // 唯一请求标识
|
||||
RiskRank uint8 `json:"risk_rank"` // 用户风险等级
|
||||
}
|
||||
|
||||
// GetUserRiskRank 根据提交的用户信息数据获取用户的安全等级 risk_rank,无需用户授权。
|
||||
func (riskControl *RiskControl) GetUserRiskRank(in *UserRiskRankRequest) (res UserRiskRank, err error) {
|
||||
accessToken, err := riskControl.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf(getUserRiskRankURL, accessToken)
|
||||
response, err := util.PostJSON(uri, in)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 使用通用方法返回错误
|
||||
err = util.DecodeWithError(response, &res, "GetUserRiskRank")
|
||||
return
|
||||
}
|
||||
256
miniprogram/security/security.go
Normal file
256
miniprogram/security/security.go
Normal file
@@ -0,0 +1,256 @@
|
||||
package security
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/silenceper/wechat/v2/miniprogram/context"
|
||||
"github.com/silenceper/wechat/v2/util"
|
||||
)
|
||||
|
||||
const (
|
||||
mediaCheckAsyncURL = "https://api.weixin.qq.com/wxa/media_check_async?access_token=%s"
|
||||
imageCheckURL = "https://api.weixin.qq.com/wxa/img_sec_check?access_token=%s"
|
||||
msgCheckURL = "https://api.weixin.qq.com/wxa/msg_sec_check?access_token=%s"
|
||||
)
|
||||
|
||||
// Security 内容安全
|
||||
type Security struct {
|
||||
*context.Context
|
||||
}
|
||||
|
||||
// NewSecurity init
|
||||
func NewSecurity(ctx *context.Context) *Security {
|
||||
return &Security{ctx}
|
||||
}
|
||||
|
||||
// MediaCheckAsyncV1Request 图片/音频异步校验请求参数
|
||||
type MediaCheckAsyncV1Request struct {
|
||||
MediaURL string `json:"media_url"` // 要检测的图片或音频的url,支持图片格式包括jpg, jepg, png, bmp, gif(取首帧),支持的音频格式包括mp3, aac, ac3, wma, flac, vorbis, opus, wav
|
||||
MediaType uint8 `json:"media_type"` // 1:音频;2:图片
|
||||
}
|
||||
|
||||
// MediaCheckAsyncV1 异步校验图片/音频是否含有违法违规内容
|
||||
// Deprecated
|
||||
// 在2021年9月1日停止更新,请尽快更新至 2.0 接口。建议使用 MediaCheckAsync
|
||||
func (security *Security) MediaCheckAsyncV1(in *MediaCheckAsyncV1Request) (traceID string, err error) {
|
||||
accessToken, err := security.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf(mediaCheckAsyncURL, accessToken)
|
||||
response, err := util.PostJSON(uri, in)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 使用通用方法返回错误
|
||||
var res struct {
|
||||
util.CommonError
|
||||
TraceID string `json:"trace_id"`
|
||||
}
|
||||
err = util.DecodeWithError(response, &res, "MediaCheckAsyncV1")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
traceID = res.TraceID
|
||||
return
|
||||
}
|
||||
|
||||
// MediaCheckAsyncRequest 图片/音频异步校验请求参数
|
||||
type MediaCheckAsyncRequest struct {
|
||||
MediaURL string `json:"media_url"` // 要检测的图片或音频的url,支持图片格式包括jpg, jepg, png, bmp, gif(取首帧),支持的音频格式包括mp3, aac, ac3, wma, flac, vorbis, opus, wav
|
||||
MediaType uint8 `json:"media_type"` // 1:音频;2:图片
|
||||
OpenID string `json:"openid"` // 用户的openid(用户需在近两小时访问过小程序)
|
||||
Scene uint8 `json:"scene"` // 场景枚举值(1 资料;2 评论;3 论坛;4 社交日志)
|
||||
}
|
||||
|
||||
// MediaCheckAsync 异步校验图片/音频是否含有违法违规内容
|
||||
func (security *Security) MediaCheckAsync(in *MediaCheckAsyncRequest) (traceID string, err error) {
|
||||
accessToken, err := security.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
MediaCheckAsyncRequest
|
||||
Version uint `json:"version"` // 接口版本号,2.0版本为固定值2
|
||||
}
|
||||
req.MediaCheckAsyncRequest = *in
|
||||
req.Version = 2
|
||||
|
||||
uri := fmt.Sprintf(mediaCheckAsyncURL, accessToken)
|
||||
response, err := util.PostJSON(uri, req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 使用通用方法返回错误
|
||||
var res struct {
|
||||
util.CommonError
|
||||
TraceID string `json:"trace_id"`
|
||||
}
|
||||
err = util.DecodeWithError(response, &res, "MediaCheckAsync")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
traceID = res.TraceID
|
||||
return
|
||||
}
|
||||
|
||||
// ImageCheckV1 校验一张图片是否含有违法违规内容(同步)
|
||||
// https://developers.weixin.qq.com/miniprogram/dev/framework/security.imgSecCheck.html
|
||||
// Deprecated
|
||||
// 在2021年9月1日停止更新。建议使用 MediaCheckAsync
|
||||
func (security *Security) ImageCheckV1(filename string) (err error) {
|
||||
accessToken, err := security.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf(imageCheckURL, accessToken)
|
||||
response, err := util.PostFile("media", filename, uri)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 使用通用方法返回错误
|
||||
return util.DecodeWithCommonError(response, "ImageCheckV1")
|
||||
}
|
||||
|
||||
// CheckSuggest 检查建议
|
||||
type CheckSuggest string
|
||||
|
||||
const (
|
||||
// CheckSuggestRisky 违规风险建议
|
||||
CheckSuggestRisky CheckSuggest = "risky"
|
||||
// CheckSuggestPass 安全
|
||||
CheckSuggestPass CheckSuggest = "pass"
|
||||
// CheckSuggestReview 需要审查
|
||||
CheckSuggestReview CheckSuggest = "review"
|
||||
)
|
||||
|
||||
// MsgScene 文本场景
|
||||
type MsgScene uint8
|
||||
|
||||
const (
|
||||
// MsgSceneMaterial 资料文件检查场景
|
||||
MsgSceneMaterial MsgScene = iota + 1
|
||||
// MsgSceneComment 评论
|
||||
MsgSceneComment
|
||||
// MsgSceneForum 论坛
|
||||
MsgSceneForum
|
||||
// MsgSceneSocialLog 社交日志
|
||||
MsgSceneSocialLog
|
||||
)
|
||||
|
||||
// CheckLabel 检查命中标签
|
||||
type CheckLabel int
|
||||
|
||||
func (cl CheckLabel) String() string {
|
||||
switch cl {
|
||||
case 100:
|
||||
return "正常"
|
||||
case 10001:
|
||||
return "广告"
|
||||
case 20001:
|
||||
return "时政"
|
||||
case 20002:
|
||||
return "色情"
|
||||
case 20003:
|
||||
return "辱骂"
|
||||
case 20006:
|
||||
return "违法犯罪"
|
||||
case 20008:
|
||||
return "欺诈"
|
||||
case 20012:
|
||||
return "低俗"
|
||||
case 20013:
|
||||
return "版权"
|
||||
case 21000:
|
||||
return "其他"
|
||||
default:
|
||||
return strconv.Itoa(int(cl))
|
||||
}
|
||||
}
|
||||
|
||||
// MsgCheckRequest 文本检查请求
|
||||
type MsgCheckRequest struct {
|
||||
OpenID string `json:"openid"` // 用户的openid(用户需在近两小时访问过小程序)
|
||||
Scene MsgScene `json:"scene"` // 场景枚举值(1 资料;2 评论;3 论坛;4 社交日志)
|
||||
Content string `json:"content"` // 需检测的文本内容,文本字数的上限为 2500 字,需使用 UTF-8 编码
|
||||
Nickname string `json:"nickname"` // (非必填)用户昵称,需使用UTF-8编码
|
||||
Title string `json:"title"` // (非必填)文本标题,需使用UTF-8编码
|
||||
Signature string `json:"signature"` // (非必填)个性签名,该参数仅在资料类场景有效(scene=1),需使用UTF-8编码
|
||||
}
|
||||
|
||||
// MsgCheckResponse 文本检查响应
|
||||
type MsgCheckResponse struct {
|
||||
util.CommonError
|
||||
TraceID string `json:"trace_id"` // 唯一请求标识
|
||||
Result struct {
|
||||
Suggest CheckSuggest `json:"suggest"` // 建议
|
||||
Label CheckLabel `json:"label"` // 命中标签
|
||||
} `json:"result"` // 综合结果
|
||||
Detail []struct {
|
||||
ErrCode int64 `json:"errcode"` // 错误码,仅当该值为0时,该项结果有效
|
||||
Strategy string `json:"strategy"` // 策略类型
|
||||
Suggest string `json:"suggest"` // 建议
|
||||
Label CheckLabel `json:"label"` // 命中标签
|
||||
Prob uint `json:"prob"` // 置信度。0-100,越高代表越有可能属于当前返回的标签(label)
|
||||
Keyword string `json:"keyword"` // 命中的自定义关键词
|
||||
} `json:"detail"` // 详细检测结果
|
||||
}
|
||||
|
||||
// MsgCheckV1 检查一段文本是否含有违法违规内容
|
||||
// Deprecated
|
||||
// 在2021年9月1日停止更新,请尽快更新至 2.0 接口。建议使用 MsgCheck
|
||||
func (security *Security) MsgCheckV1(content string) (res MsgCheckResponse, err error) {
|
||||
accessToken, err := security.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Content string `json:"content"`
|
||||
}
|
||||
req.Content = content
|
||||
|
||||
uri := fmt.Sprintf(msgCheckURL, accessToken)
|
||||
response, err := util.PostJSON(uri, req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 使用通用方法返回错误
|
||||
err = util.DecodeWithError(response, &res, "security.MsgCheckV1")
|
||||
return
|
||||
}
|
||||
|
||||
// MsgCheck 检查一段文本是否含有违法违规内容
|
||||
func (security *Security) MsgCheck(in *MsgCheckRequest) (res MsgCheckResponse, err error) {
|
||||
accessToken, err := security.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
MsgCheckRequest
|
||||
Version uint `json:"version"`
|
||||
}
|
||||
req.MsgCheckRequest = *in
|
||||
req.Version = 2
|
||||
|
||||
uri := fmt.Sprintf(msgCheckURL, accessToken)
|
||||
response, err := util.PostJSON(uri, req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 使用通用方法返回错误
|
||||
err = util.DecodeWithError(response, &res, "security.MsgCheck")
|
||||
return
|
||||
}
|
||||
@@ -121,7 +121,7 @@ type UniformMessage struct {
|
||||
URL string `json:"url"`
|
||||
Miniprogram struct {
|
||||
Appid string `json:"appid"`
|
||||
Pagepath string `json:"pagepath"`
|
||||
Pagepath string `json:"page"`
|
||||
} `json:"miniprogram"`
|
||||
Data map[string]*DataItem `json:"data"`
|
||||
} `json:"mp_template_msg"`
|
||||
|
||||
@@ -21,7 +21,9 @@ wcTcb := wc.GetTcb()
|
||||
```
|
||||
|
||||
### 举例
|
||||
|
||||
#### 触发云函数
|
||||
|
||||
```golang
|
||||
res, err := wcTcb.InvokeCloudFunction("test-xxxx", "add", `{"a":1,"b":2}`)
|
||||
if err != nil {
|
||||
|
||||
52
miniprogram/urllink/query.go
Normal file
52
miniprogram/urllink/query.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package urllink
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/silenceper/wechat/v2/util"
|
||||
)
|
||||
|
||||
const queryURL = "https://api.weixin.qq.com/wxa/query_urllink"
|
||||
|
||||
// ULQueryResult 返回的结果
|
||||
// https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/url-link/urllink.query.html 返回值
|
||||
type ULQueryResult struct {
|
||||
util.CommonError
|
||||
|
||||
URLLinkInfo struct {
|
||||
Appid string `json:"appid"`
|
||||
Path string `json:"path"`
|
||||
Query string `json:"query"`
|
||||
CreateTime int64 `json:"create_time"`
|
||||
ExpireTime int64 `json:"expire_time"`
|
||||
EnvVersion string `json:"env_version"`
|
||||
CloudBase struct {
|
||||
Env string `json:"env"`
|
||||
Domain string `json:"domain"`
|
||||
Path string `json:"path"`
|
||||
Query string `json:"query"`
|
||||
ResourceAppid string `json:"resource_appid"`
|
||||
} `json:"cloud_base"`
|
||||
} `json:"url_link_info"`
|
||||
VisitOpenid string `json:"visit_openid"`
|
||||
}
|
||||
|
||||
// Query 查询小程序 url_link 配置。
|
||||
func (u *URLLink) Query(urlLink string) (*ULQueryResult, error) {
|
||||
accessToken, err := u.GetAccessToken()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf("%s?access_token=%s", queryURL, accessToken)
|
||||
response, err := util.PostJSON(uri, map[string]string{"url_link": urlLink})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var resp ULQueryResult
|
||||
err = util.DecodeWithError(response, &resp, "URLLink.Query")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &resp, nil
|
||||
}
|
||||
70
miniprogram/urlscheme/query.go
Normal file
70
miniprogram/urlscheme/query.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package urlscheme
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/silenceper/wechat/v2/util"
|
||||
)
|
||||
|
||||
const (
|
||||
querySchemeURL = "https://api.weixin.qq.com/wxa/queryscheme?access_token=%s"
|
||||
)
|
||||
|
||||
// QueryScheme 获取小程序访问scheme
|
||||
// https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/url-scheme/urlscheme.query.html#参数
|
||||
type QueryScheme struct {
|
||||
// 小程序 scheme 码
|
||||
Scheme string `json:"scheme"`
|
||||
}
|
||||
|
||||
// SchemeInfo scheme 配置
|
||||
type SchemeInfo struct {
|
||||
// 小程序 appid。
|
||||
AppID string `json:"appid"`
|
||||
// 小程序页面路径。
|
||||
Path string `json:"path"`
|
||||
// 小程序页面query。
|
||||
Query string `json:"query"`
|
||||
// 创建时间,为 Unix 时间戳。
|
||||
CreateTime int64 `json:"create_time"`
|
||||
// 到期失效时间,为 Unix 时间戳,0 表示永久生效
|
||||
ExpireTime int64 `json:"expire_time"`
|
||||
// 要打开的小程序版本。正式版为"release",体验版为"trial",开发版为"develop"。
|
||||
EnvVersion EnvVersion `json:"env_version"`
|
||||
}
|
||||
|
||||
// resQueryScheme 返回结构体
|
||||
// https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/url-scheme/urlscheme.query.html#参数
|
||||
type resQueryScheme struct {
|
||||
// 通用错误
|
||||
*util.CommonError
|
||||
// scheme 配置
|
||||
SchemeInfo SchemeInfo `json:"scheme_info"`
|
||||
// 访问该链接的openid,没有用户访问过则为空字符串
|
||||
VisitOpenid string `json:"visit_openid"`
|
||||
}
|
||||
|
||||
// QueryScheme 查询小程序 scheme 码
|
||||
func (u *URLScheme) QueryScheme(querySchemeParams QueryScheme) (schemeInfo SchemeInfo, visitOpenid string, err error) {
|
||||
var accessToken string
|
||||
accessToken, err = u.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
urlStr := fmt.Sprintf(querySchemeURL, accessToken)
|
||||
var response []byte
|
||||
response, err = util.PostJSON(urlStr, querySchemeParams)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 使用通用方法返回错误
|
||||
var res resQueryScheme
|
||||
err = util.DecodeWithError(response, &res, "QueryScheme")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return res.SchemeInfo, res.VisitOpenid, nil
|
||||
}
|
||||
85
miniprogram/urlscheme/urlscheme.go
Normal file
85
miniprogram/urlscheme/urlscheme.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package urlscheme
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/silenceper/wechat/v2/miniprogram/context"
|
||||
"github.com/silenceper/wechat/v2/util"
|
||||
)
|
||||
|
||||
// URLScheme 小程序 URL Scheme
|
||||
type URLScheme struct {
|
||||
*context.Context
|
||||
}
|
||||
|
||||
// NewURLScheme 实例化
|
||||
func NewURLScheme(ctx *context.Context) *URLScheme {
|
||||
return &URLScheme{Context: ctx}
|
||||
}
|
||||
|
||||
const generateURL = "https://api.weixin.qq.com/wxa/generatescheme"
|
||||
|
||||
// TExpireType 失效类型 (指定时间戳/指定间隔)
|
||||
type TExpireType int
|
||||
|
||||
// EnvVersion 要打开的小程序版本
|
||||
type EnvVersion string
|
||||
|
||||
const (
|
||||
// ExpireTypeTime 指定时间戳后失效
|
||||
ExpireTypeTime TExpireType = 0
|
||||
// ExpireTypeInterval 间隔指定天数后失效
|
||||
ExpireTypeInterval TExpireType = 1
|
||||
|
||||
// EnvVersionRelease 正式版为"release"
|
||||
EnvVersionRelease EnvVersion = "release"
|
||||
// EnvVersionTrial 体验版为"trial"
|
||||
EnvVersionTrial EnvVersion = "trial"
|
||||
// EnvVersionDevelop 开发版为"develop"
|
||||
EnvVersionDevelop EnvVersion = "develop"
|
||||
)
|
||||
|
||||
// JumpWxa 跳转到的目标小程序信息
|
||||
type JumpWxa struct {
|
||||
Path string `json:"path"`
|
||||
Query string `json:"query"`
|
||||
// envVersion 要打开的小程序版本。正式版为 "release",体验版为 "trial",开发版为 "develop"
|
||||
EnvVersion EnvVersion `json:"env_version,omitempty"`
|
||||
}
|
||||
|
||||
// USParams 请求参数
|
||||
// https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/url-scheme/urlscheme.generate.html#请求参数
|
||||
type USParams struct {
|
||||
JumpWxa *JumpWxa `json:"jump_wxa"`
|
||||
ExpireType TExpireType `json:"expire_type"`
|
||||
ExpireTime int64 `json:"expire_time"`
|
||||
ExpireInterval int `json:"expire_interval"`
|
||||
}
|
||||
|
||||
// USResult 返回的结果
|
||||
// https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/url-scheme/urlscheme.generate.html#返回值
|
||||
type USResult struct {
|
||||
util.CommonError
|
||||
|
||||
OpenLink string `json:"openlink"`
|
||||
}
|
||||
|
||||
// Generate 生成url link
|
||||
func (u *URLScheme) Generate(params *USParams) (string, error) {
|
||||
accessToken, err := u.GetAccessToken()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf("%s?access_token=%s", generateURL, accessToken)
|
||||
response, err := util.PostJSON(uri, params)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var resp USResult
|
||||
err = util.DecodeWithError(response, &resp, "URLScheme.Generate")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return resp.OpenLink, nil
|
||||
}
|
||||
228
officialaccount/draft/draft.go
Normal file
228
officialaccount/draft/draft.go
Normal file
@@ -0,0 +1,228 @@
|
||||
package draft
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/silenceper/wechat/v2/officialaccount/context"
|
||||
"github.com/silenceper/wechat/v2/util"
|
||||
)
|
||||
|
||||
const (
|
||||
addURL = "https://api.weixin.qq.com/cgi-bin/draft/add" // 新建草稿
|
||||
getURL = "https://api.weixin.qq.com/cgi-bin/draft/get" // 获取草稿
|
||||
deleteURL = "https://api.weixin.qq.com/cgi-bin/draft/delete" // 删除草稿
|
||||
updateURL = "https://api.weixin.qq.com/cgi-bin/draft/update" // 修改草稿
|
||||
countURL = "https://api.weixin.qq.com/cgi-bin/draft/count" // 获取草稿总数
|
||||
paginateURL = "https://api.weixin.qq.com/cgi-bin/draft/batchget" // 获取草稿列表
|
||||
)
|
||||
|
||||
// Draft 草稿箱
|
||||
type Draft struct {
|
||||
*context.Context
|
||||
}
|
||||
|
||||
// NewDraft init
|
||||
func NewDraft(ctx *context.Context) *Draft {
|
||||
return &Draft{
|
||||
Context: ctx,
|
||||
}
|
||||
}
|
||||
|
||||
// Article 草稿
|
||||
type Article struct {
|
||||
Title string `json:"title"` // 标题
|
||||
Author string `json:"author"` // 作者
|
||||
Digest string `json:"digest"` // 图文消息的摘要,仅有单图文消息才有摘要,多图文此处为空。
|
||||
Content string `json:"content"` // 图文消息的具体内容,支持HTML标签,必须少于2万字符,小于1M,且去除JS
|
||||
ContentSourceURL string `json:"content_source_url"` // 图文消息的原文地址,即点击“阅读原文”后的URL
|
||||
ThumbMediaID string `json:"thumb_media_id"` // 图文消息的封面图片素材id(必须是永久MediaID)
|
||||
ShowCoverPic uint `json:"show_cover_pic"` // 是否显示封面,0为false,即不显示,1为true,即显示(默认)
|
||||
NeedOpenComment uint `json:"need_open_comment"` // 是否打开评论,0不打开(默认),1打开
|
||||
OnlyFansCanComment uint `json:"only_fans_can_comment"` // 是否粉丝才可评论,0所有人可评论(默认),1粉丝才可评论
|
||||
}
|
||||
|
||||
// AddDraft 新建草稿
|
||||
func (draft *Draft) AddDraft(articles []*Article) (mediaID string, err error) {
|
||||
accessToken, err := draft.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Articles []*Article `json:"articles"`
|
||||
}
|
||||
req.Articles = articles
|
||||
|
||||
uri := fmt.Sprintf("%s?access_token=%s", addURL, accessToken)
|
||||
response, err := util.PostJSON(uri, req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var res struct {
|
||||
util.CommonError
|
||||
MediaID string `json:"media_id"`
|
||||
}
|
||||
err = util.DecodeWithError(response, &res, "AddDraft")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
mediaID = res.MediaID
|
||||
return
|
||||
}
|
||||
|
||||
// GetDraft 获取草稿
|
||||
func (draft *Draft) GetDraft(mediaID string) (articles []*Article, err error) {
|
||||
accessToken, err := draft.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
MediaID string `json:"media_id"`
|
||||
}
|
||||
req.MediaID = mediaID
|
||||
|
||||
uri := fmt.Sprintf("%s?access_token=%s", getURL, accessToken)
|
||||
response, err := util.PostJSON(uri, req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var res struct {
|
||||
util.CommonError
|
||||
NewsItem []*Article `json:"news_item"`
|
||||
}
|
||||
err = util.DecodeWithError(response, &res, "GetDraft")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
articles = res.NewsItem
|
||||
return
|
||||
}
|
||||
|
||||
// DeleteDraft 删除草稿
|
||||
func (draft *Draft) DeleteDraft(mediaID string) (err error) {
|
||||
accessToken, err := draft.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
MediaID string `json:"media_id"`
|
||||
}
|
||||
req.MediaID = mediaID
|
||||
|
||||
var response []byte
|
||||
uri := fmt.Sprintf("%s?access_token=%s", deleteURL, accessToken)
|
||||
response, err = util.PostJSON(uri, req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = util.DecodeWithCommonError(response, "DeleteDraft")
|
||||
return
|
||||
}
|
||||
|
||||
// UpdateDraft 修改草稿
|
||||
// index 要更新的文章在图文消息中的位置(多图文消息时,此字段才有意义),第一篇为0
|
||||
func (draft *Draft) UpdateDraft(article *Article, mediaID string, index uint) (err error) {
|
||||
accessToken, err := draft.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
MediaID string `json:"media_id"`
|
||||
Index uint `json:"index"`
|
||||
Article *Article `json:"articles"`
|
||||
}
|
||||
req.MediaID = mediaID
|
||||
req.Index = index
|
||||
req.Article = article
|
||||
|
||||
uri := fmt.Sprintf("%s?access_token=%s", updateURL, accessToken)
|
||||
var response []byte
|
||||
response, err = util.PostJSON(uri, req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = util.DecodeWithCommonError(response, "UpdateDraft")
|
||||
return
|
||||
}
|
||||
|
||||
// CountDraft 获取草稿总数
|
||||
func (draft *Draft) CountDraft() (total uint, err error) {
|
||||
accessToken, err := draft.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var response []byte
|
||||
uri := fmt.Sprintf("%s?access_token=%s", countURL, accessToken)
|
||||
response, err = util.HTTPGet(uri)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var res struct {
|
||||
util.CommonError
|
||||
Total uint `json:"total_count"`
|
||||
}
|
||||
err = util.DecodeWithError(response, &res, "CountDraft")
|
||||
if nil != err {
|
||||
return
|
||||
}
|
||||
|
||||
total = res.Total
|
||||
return
|
||||
}
|
||||
|
||||
// ArticleList 草稿列表
|
||||
type ArticleList struct {
|
||||
util.CommonError
|
||||
TotalCount int64 `json:"total_count"` // 草稿素材的总数
|
||||
ItemCount int64 `json:"item_count"` // 本次调用获取的素材的数量
|
||||
Item []ArticleListItem `json:"item"`
|
||||
}
|
||||
|
||||
// ArticleListItem 用于 ArticleList 的 item 节点
|
||||
type ArticleListItem struct {
|
||||
MediaID string `json:"media_id"` // 图文消息的id
|
||||
Content ArticleListContent `json:"content"` // 内容
|
||||
UpdateTime int64 `json:"update_time"` // 这篇图文消息素材的最后更新时间
|
||||
}
|
||||
|
||||
// ArticleListContent 用于 ArticleListItem 的 content 节点
|
||||
type ArticleListContent struct {
|
||||
NewsItem []Article `json:"news_item"` // 这篇图文消息素材的内容
|
||||
}
|
||||
|
||||
// PaginateDraft 获取草稿列表
|
||||
func (draft *Draft) PaginateDraft(offset, count int64, noReturnContent bool) (list ArticleList, err error) {
|
||||
accessToken, err := draft.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Count int64 `json:"count"`
|
||||
Offset int64 `json:"offset"`
|
||||
NoReturnContent bool `json:"no_content"`
|
||||
}
|
||||
req.Count = count
|
||||
req.Offset = offset
|
||||
req.NoReturnContent = noReturnContent
|
||||
|
||||
var response []byte
|
||||
uri := fmt.Sprintf("%s?access_token=%s", paginateURL, accessToken)
|
||||
response, err = util.PostJSON(uri, req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = util.DecodeWithError(response, &list, "PaginateDraft")
|
||||
return
|
||||
}
|
||||
248
officialaccount/freepublish/freepublish.go
Normal file
248
officialaccount/freepublish/freepublish.go
Normal file
@@ -0,0 +1,248 @@
|
||||
package freepublish
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/silenceper/wechat/v2/officialaccount/context"
|
||||
"github.com/silenceper/wechat/v2/util"
|
||||
)
|
||||
|
||||
const (
|
||||
publishURL = "https://api.weixin.qq.com/cgi-bin/freepublish/submit" // 发布接口
|
||||
selectStateURL = "https://api.weixin.qq.com/cgi-bin/freepublish/get" // 发布状态轮询接口
|
||||
deleteURL = "https://api.weixin.qq.com/cgi-bin/freepublish/delete" // 删除发布
|
||||
firstArticleURL = "https://api.weixin.qq.com/cgi-bin/freepublish/getarticle" // 通过 article_id 获取已发布文章
|
||||
paginateURL = "https://api.weixin.qq.com/cgi-bin/freepublish/batchget" // 获取成功发布列表
|
||||
)
|
||||
|
||||
// PublishStatus 发布状态
|
||||
type PublishStatus uint
|
||||
|
||||
const (
|
||||
// PublishStatusSuccess 0:成功
|
||||
PublishStatusSuccess PublishStatus = iota
|
||||
// PublishStatusPublishing 1:发布中
|
||||
PublishStatusPublishing
|
||||
// PublishStatusOriginalFail 2:原创失败
|
||||
PublishStatusOriginalFail
|
||||
// PublishStatusFail 3:常规失败
|
||||
PublishStatusFail
|
||||
// PublishStatusAuditRefused 4:平台审核不通过
|
||||
PublishStatusAuditRefused
|
||||
// PublishStatusUserDeleted 5:成功后用户删除所有文章
|
||||
PublishStatusUserDeleted
|
||||
// PublishStatusSystemBanned 6:成功后系统封禁所有文章
|
||||
PublishStatusSystemBanned
|
||||
)
|
||||
|
||||
// FreePublish 发布能力
|
||||
type FreePublish struct {
|
||||
*context.Context
|
||||
}
|
||||
|
||||
// NewFreePublish init
|
||||
func NewFreePublish(ctx *context.Context) *FreePublish {
|
||||
return &FreePublish{
|
||||
Context: ctx,
|
||||
}
|
||||
}
|
||||
|
||||
// Publish 发布接口。需要先将图文素材以草稿的形式保存(见“草稿箱/新建草稿”,
|
||||
// 如需从已保存的草稿中选择,见“草稿箱/获取草稿列表”),选择要发布的草稿 media_id 进行发布
|
||||
func (freePublish *FreePublish) Publish(mediaID string) (publishID int64, err error) {
|
||||
var accessToken string
|
||||
accessToken, err = freePublish.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
MediaID string `json:"media_id"`
|
||||
}
|
||||
req.MediaID = mediaID
|
||||
|
||||
var response []byte
|
||||
uri := fmt.Sprintf("%s?access_token=%s", publishURL, accessToken)
|
||||
response, err = util.PostJSON(uri, req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var res struct {
|
||||
util.CommonError
|
||||
PublishID int64 `json:"publish_id"`
|
||||
}
|
||||
err = util.DecodeWithError(response, &res, "SubmitFreePublish")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
publishID = res.PublishID
|
||||
return
|
||||
}
|
||||
|
||||
// PublishStatusList 发布任务状态列表
|
||||
type PublishStatusList struct {
|
||||
util.CommonError
|
||||
PublishID int64 `json:"publish_id"` // 发布任务id
|
||||
PublishStatus PublishStatus `json:"publish_status"` // 发布状态
|
||||
ArticleID string `json:"article_id"` // 当发布状态为0时(即成功)时,返回图文的 article_id,可用于“客服消息”场景
|
||||
ArticleDetail PublishArticleDetail `json:"article_detail"` // 发布任务文章成功状态详情
|
||||
FailIndex []uint `json:"fail_idx"` // 当发布状态为2或4时,返回不通过的文章编号,第一篇为 1;其他发布状态则为空
|
||||
}
|
||||
|
||||
// PublishArticleDetail 发布任务成功详情
|
||||
type PublishArticleDetail struct {
|
||||
Count uint `json:"count"` // 当发布状态为0时(即成功)时,返回文章数量
|
||||
Items []PublishArticleItem `json:"item"`
|
||||
}
|
||||
|
||||
// PublishArticleItem 发布任务成功的文章内容
|
||||
type PublishArticleItem struct {
|
||||
Index uint `json:"idx"` // 当发布状态为0时(即成功)时,返回文章对应的编号
|
||||
ArticleURL string `json:"article_url"` // 当发布状态为0时(即成功)时,返回图文的永久链接
|
||||
}
|
||||
|
||||
// SelectStatus 发布状态轮询接口
|
||||
func (freePublish *FreePublish) SelectStatus(publishID int64) (list PublishStatusList, err error) {
|
||||
accessToken, err := freePublish.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
PublishID int64 `json:"publish_id"`
|
||||
}
|
||||
req.PublishID = publishID
|
||||
|
||||
var response []byte
|
||||
uri := fmt.Sprintf("%s?access_token=%s", selectStateURL, accessToken)
|
||||
response, err = util.PostJSON(uri, req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = util.DecodeWithError(response, &list, "SelectStatusFreePublish")
|
||||
return
|
||||
}
|
||||
|
||||
// Delete 删除发布。
|
||||
// index 要删除的文章在图文消息中的位置,第一篇编号为1,该字段不填或填0会删除全部文章
|
||||
// !!!此操作不可逆,请谨慎操作!!!删除后微信公众号后台仍然会有记录!!!
|
||||
func (freePublish *FreePublish) Delete(articleID string, index uint) (err error) {
|
||||
accessToken, err := freePublish.GetAccessToken()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var req struct {
|
||||
ArticleID string `json:"article_id"`
|
||||
Index uint `json:"index"`
|
||||
}
|
||||
req.ArticleID = articleID
|
||||
req.Index = index
|
||||
|
||||
var response []byte
|
||||
uri := fmt.Sprintf("%s?access_token=%s", deleteURL, accessToken)
|
||||
response, err = util.PostJSON(uri, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return util.DecodeWithCommonError(response, "DeleteFreePublish")
|
||||
}
|
||||
|
||||
// Article 图文信息内容
|
||||
type Article struct {
|
||||
Title string `json:"title"` // 标题
|
||||
Author string `json:"author"` // 作者
|
||||
Digest string `json:"digest"` // 图文消息的摘要,仅有单图文消息才有摘要,多图文此处为空
|
||||
Content string `json:"content"` // 图文消息的具体内容,支持HTML标签,必须少于2万字符,小于1M,且此处会去除JS
|
||||
ContentSourceURL string `json:"content_source_url"` // 图文消息的原文地址,即点击“阅读原文”后的URL
|
||||
ThumbMediaID string `json:"thumb_media_id"` // 图文消息的封面图片素材id(一定是永久MediaID)
|
||||
ShowCoverPic uint `json:"show_cover_pic"` // 是否显示封面,0为false,即不显示,1为true,即显示(默认)
|
||||
NeedOpenComment uint `json:"need_open_comment"` // 是否打开评论,0不打开(默认),1打开
|
||||
OnlyFansCanComment uint `json:"only_fans_can_comment"` // 是否粉丝才可评论,0所有人可评论(默认),1粉丝才可评论
|
||||
URL string `json:"url"` // 图文消息的URL
|
||||
IsDeleted bool `json:"is_deleted"` // 该图文是否被删除
|
||||
}
|
||||
|
||||
// First 通过 article_id 获取已发布文章
|
||||
func (freePublish *FreePublish) First(articleID string) (list []Article, err error) {
|
||||
accessToken, err := freePublish.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
ArticleID string `json:"article_id"`
|
||||
}
|
||||
req.ArticleID = articleID
|
||||
|
||||
var response []byte
|
||||
uri := fmt.Sprintf("%s?access_token=%s", firstArticleURL, accessToken)
|
||||
response, err = util.PostJSON(uri, req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var res struct {
|
||||
util.CommonError
|
||||
NewsItem []Article `json:"news_item"`
|
||||
}
|
||||
err = util.DecodeWithError(response, &res, "FirstFreePublish")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
list = res.NewsItem
|
||||
return
|
||||
}
|
||||
|
||||
// ArticleList 发布列表
|
||||
type ArticleList struct {
|
||||
util.CommonError
|
||||
TotalCount int64 `json:"total_count"` // 成功发布素材的总数
|
||||
ItemCount int64 `json:"item_count"` // 本次调用获取的素材的数量
|
||||
Item []ArticleListItem `json:"item"`
|
||||
}
|
||||
|
||||
// ArticleListItem 用于 ArticleList 的 item 节点
|
||||
type ArticleListItem struct {
|
||||
ArticleID string `json:"article_id"` // 成功发布的图文消息id
|
||||
Content ArticleListContent `json:"content"` // 内容
|
||||
UpdateTime int64 `json:"update_time"` // 这篇图文消息素材的最后更新时间
|
||||
}
|
||||
|
||||
// ArticleListContent 用于 ArticleListItem 的 content 节点
|
||||
type ArticleListContent struct {
|
||||
NewsItem []Article `json:"news_item"` // 这篇图文消息素材的内容
|
||||
}
|
||||
|
||||
// Paginate 获取成功发布列表
|
||||
func (freePublish *FreePublish) Paginate(offset, count int64, noReturnContent bool) (list ArticleList, err error) {
|
||||
var accessToken string
|
||||
accessToken, err = freePublish.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Count int64 `json:"count"`
|
||||
Offset int64 `json:"offset"`
|
||||
NoReturnContent bool `json:"no_content"`
|
||||
}
|
||||
req.Count = count
|
||||
req.Offset = offset
|
||||
req.NoReturnContent = noReturnContent
|
||||
|
||||
var response []byte
|
||||
uri := fmt.Sprintf("%s?access_token=%s", paginateURL, accessToken)
|
||||
response, err = util.PostJSON(uri, req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = util.DecodeWithError(response, &list, "PaginateFreePublish")
|
||||
return
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/xml"
|
||||
|
||||
"github.com/silenceper/wechat/v2/officialaccount/device"
|
||||
"github.com/silenceper/wechat/v2/officialaccount/freepublish"
|
||||
)
|
||||
|
||||
// MsgType 基本消息类型
|
||||
@@ -67,6 +68,8 @@ const (
|
||||
EventPicWeixin EventType = "pic_weixin"
|
||||
// EventLocationSelect 弹出地理位置选择器的事件推送
|
||||
EventLocationSelect EventType = "location_select"
|
||||
// EventViewMiniprogram 点击菜单跳转小程序的事件推送
|
||||
EventViewMiniprogram EventType = "view_miniprogram"
|
||||
// EventTemplateSendJobFinish 发送模板消息推送通知
|
||||
EventTemplateSendJobFinish EventType = "TEMPLATESENDJOBFINISH"
|
||||
// EventMassSendJobFinish 群发消息推送通知
|
||||
@@ -75,6 +78,8 @@ const (
|
||||
EventWxaMediaCheck EventType = "wxa_media_check"
|
||||
// EventSubscribeMsgPopupEvent 订阅通知事件推送
|
||||
EventSubscribeMsgPopupEvent EventType = "subscribe_msg_popup_event"
|
||||
// EventPublishJobFinish 发布任务完成
|
||||
EventPublishJobFinish EventType = "PUBLISHJOBFINISH"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -114,7 +119,7 @@ type MixMessage struct {
|
||||
URL string `xml:"Url"`
|
||||
|
||||
// 事件相关
|
||||
Event EventType `xml:"Event"`
|
||||
Event EventType `xml:"Event" json:"Event"`
|
||||
EventKey string `xml:"EventKey"`
|
||||
Ticket string `xml:"Ticket"`
|
||||
Latitude string `xml:"Latitude"`
|
||||
@@ -146,10 +151,27 @@ type MixMessage struct {
|
||||
Poiname string `xml:"Poiname"`
|
||||
}
|
||||
|
||||
subscribeMsgPopupEventList []SubscribeMsgPopupEvent `json:"-"`
|
||||
|
||||
SubscribeMsgPopupEvent []struct {
|
||||
List SubscribeMsgPopupEvent `xml:"List"`
|
||||
} `xml:"SubscribeMsgPopupEvent"`
|
||||
|
||||
// 事件相关:发布能力
|
||||
PublishEventInfo struct {
|
||||
PublishID int64 `xml:"publish_id"` // 发布任务id
|
||||
PublishStatus freepublish.PublishStatus `xml:"publish_status"` // 发布状态
|
||||
ArticleID string `xml:"article_id"` // 当发布状态为0时(即成功)时,返回图文的 article_id,可用于“客服消息”场景
|
||||
ArticleDetail struct {
|
||||
Count uint `xml:"count"` // 文章数量
|
||||
Item []struct {
|
||||
Index uint `xml:"idx"` // 文章对应的编号
|
||||
ArticleURL string `xml:"article_url"` // 图文的永久链接
|
||||
} `xml:"item"`
|
||||
} `xml:"article_detail"` // 当发布状态为0时(即成功)时,返回内容
|
||||
FailIndex []uint `xml:"fail_idx"` // 当发布状态为2或4时,返回不通过的文章编号,第一篇为 1;其他发布状态则为空
|
||||
} `xml:"PublishEventInfo"`
|
||||
|
||||
// 第三方平台相关
|
||||
InfoType InfoType `xml:"InfoType"`
|
||||
AppID string `xml:"AppId"`
|
||||
@@ -191,9 +213,26 @@ type MixMessage struct {
|
||||
|
||||
// SubscribeMsgPopupEvent 订阅通知事件推送的消息体
|
||||
type SubscribeMsgPopupEvent struct {
|
||||
TemplateID string `xml:"TemplateId"`
|
||||
SubscribeStatusString string `xml:"SubscribeStatusString"`
|
||||
PopupScene int `xml:"PopupScene"`
|
||||
TemplateID string `xml:"TemplateId" json:"TemplateId"`
|
||||
SubscribeStatusString string `xml:"SubscribeStatusString" json:"SubscribeStatusString"`
|
||||
PopupScene int `xml:"PopupScene" json:"PopupScene,string"`
|
||||
}
|
||||
|
||||
// SetSubscribeMsgPopupEvents 设置订阅消息事件
|
||||
func (s *MixMessage) SetSubscribeMsgPopupEvents(list []SubscribeMsgPopupEvent) {
|
||||
s.subscribeMsgPopupEventList = list
|
||||
}
|
||||
|
||||
// GetSubscribeMsgPopupEvents 获取订阅消息事件数据
|
||||
func (s *MixMessage) GetSubscribeMsgPopupEvents() []SubscribeMsgPopupEvent {
|
||||
if s.subscribeMsgPopupEventList != nil {
|
||||
return s.subscribeMsgPopupEventList
|
||||
}
|
||||
list := make([]SubscribeMsgPopupEvent, len(s.SubscribeMsgPopupEvent))
|
||||
for i, item := range s.SubscribeMsgPopupEvent {
|
||||
list[i] = item.List
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// EventPic 发图事件推送
|
||||
@@ -230,10 +269,10 @@ func (c CDATA) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
||||
// CommonToken 消息中通用的结构
|
||||
type CommonToken struct {
|
||||
XMLName xml.Name `xml:"xml"`
|
||||
ToUserName CDATA `xml:"ToUserName"`
|
||||
FromUserName CDATA `xml:"FromUserName"`
|
||||
CreateTime int64 `xml:"CreateTime"`
|
||||
MsgType MsgType `xml:"MsgType"`
|
||||
ToUserName CDATA `xml:"ToUserName" json:"ToUserName"`
|
||||
FromUserName CDATA `xml:"FromUserName" json:"FromUserName"`
|
||||
CreateTime int64 `xml:"CreateTime" json:"CreateTime"`
|
||||
MsgType MsgType `xml:"MsgType" json:"MsgType"`
|
||||
}
|
||||
|
||||
// SetToUserName set ToUserName
|
||||
|
||||
@@ -8,10 +8,13 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
subscribeSendURL = "https://api.weixin.qq.com/cgi-bin/message/subscribe/bizsend"
|
||||
subscribeTemplateListURL = "https://api.weixin.qq.com/wxaapi/newtmpl/gettemplate"
|
||||
subscribeTemplateAddURL = "https://api.weixin.qq.com/wxaapi/newtmpl/addtemplate"
|
||||
subscribeTemplateDelURL = "https://api.weixin.qq.com/wxaapi/newtmpl/deltemplate"
|
||||
subscribeSendURL = "https://api.weixin.qq.com/cgi-bin/message/subscribe/bizsend"
|
||||
subscribeTemplateListURL = "https://api.weixin.qq.com/wxaapi/newtmpl/gettemplate"
|
||||
subscribeTemplateAddURL = "https://api.weixin.qq.com/wxaapi/newtmpl/addtemplate"
|
||||
subscribeTemplateDelURL = "https://api.weixin.qq.com/wxaapi/newtmpl/deltemplate"
|
||||
subscribeTemplateGetCategoryURL = "https://api.weixin.qq.com/wxaapi/newtmpl/getcategory"
|
||||
subscribeTemplateGetPubTplKeyWorksURL = "https://api.weixin.qq.com/wxaapi/newtmpl/getpubtemplatekeywords"
|
||||
subscribeTemplateGetPubTplTitles = "https://api.weixin.qq.com/wxaapi/newtmpl/getpubtemplatetitles"
|
||||
)
|
||||
|
||||
// Subscribe 订阅消息
|
||||
@@ -145,3 +148,108 @@ func (tpl *Subscribe) Delete(templateID string) (err error) {
|
||||
}
|
||||
return util.DecodeWithCommonError(response, "DeleteSubscribe")
|
||||
}
|
||||
|
||||
// PublicTemplateCategory 公众号类目
|
||||
type PublicTemplateCategory struct {
|
||||
ID int `json:"id"` //类目ID
|
||||
Name string `json:"name"` //类目的中文名
|
||||
}
|
||||
|
||||
type resSubscribeCategoryList struct {
|
||||
util.CommonError
|
||||
CategoryList []*PublicTemplateCategory `json:"data"`
|
||||
}
|
||||
|
||||
// GetCategory 获取公众号类目
|
||||
func (tpl *Subscribe) GetCategory() (categoryList []*PublicTemplateCategory, err error) {
|
||||
var accessToken string
|
||||
accessToken, err = tpl.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
uri := fmt.Sprintf("%s?access_token=%s", subscribeTemplateGetCategoryURL, accessToken)
|
||||
var response []byte
|
||||
response, err = util.HTTPGet(uri)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var result resSubscribeCategoryList
|
||||
err = util.DecodeWithError(response, &result, "GetCategory")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
categoryList = result.CategoryList
|
||||
return
|
||||
}
|
||||
|
||||
// PublicTemplateKeyWords 模板中的关键词
|
||||
type PublicTemplateKeyWords struct {
|
||||
KeyWordsID int `json:"kid"` // 关键词 id
|
||||
Name string `json:"name"` // 关键词内容
|
||||
Example string `json:"example"` //关键词内容对应的示例
|
||||
Rule string `json:"rule"` // 参数类型
|
||||
}
|
||||
|
||||
type resPublicTemplateKeyWordsList struct {
|
||||
util.CommonError
|
||||
KeyWordsList []*PublicTemplateKeyWords `json:"data"` //关键词列表
|
||||
}
|
||||
|
||||
// GetPubTplKeyWordsByID 获取模板中的关键词
|
||||
func (tpl *Subscribe) GetPubTplKeyWordsByID(titleID string) (keyWordsList []*PublicTemplateKeyWords, err error) {
|
||||
var accessToken string
|
||||
accessToken, err = tpl.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
uri := fmt.Sprintf("%s?access_token=%s&tid=%s", subscribeTemplateGetPubTplKeyWorksURL, accessToken, titleID)
|
||||
var response []byte
|
||||
response, err = util.HTTPGet(uri)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var result resPublicTemplateKeyWordsList
|
||||
err = util.DecodeWithError(response, &result, "GetPublicTemplateKeyWords")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
keyWordsList = result.KeyWordsList
|
||||
return
|
||||
}
|
||||
|
||||
// PublicTemplateTitle 类目下的公共模板
|
||||
type PublicTemplateTitle struct {
|
||||
TitleID int `json:"tid"` // 模版标题 id
|
||||
Title string `json:"title"` // 模版标题
|
||||
Type int `json:"type"` // 模版类型,2 为一次性订阅,3 为长期订阅
|
||||
CategoryID string `json:"categoryId"` // 模版所属类目 id
|
||||
}
|
||||
|
||||
type resPublicTemplateTitleList struct {
|
||||
util.CommonError
|
||||
Count int `json:"count"` //公共模板列表总数
|
||||
TemplateTitleList []*PublicTemplateTitle `json:"data"` //模板标题列表
|
||||
}
|
||||
|
||||
// GetPublicTemplateTitleList 获取类目下的公共模板
|
||||
func (tpl *Subscribe) GetPublicTemplateTitleList(ids string, start int, limit int) (count int, templateTitleList []*PublicTemplateTitle, err error) {
|
||||
var accessToken string
|
||||
accessToken, err = tpl.GetAccessToken()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
uri := fmt.Sprintf("%s?access_token=%s&ids=%s&start=%d&limit=%d", subscribeTemplateGetPubTplTitles, accessToken, ids, start, limit)
|
||||
var response []byte
|
||||
response, err = util.HTTPGet(uri)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var result resPublicTemplateTitleList
|
||||
err = util.DecodeWithError(response, &result, "GetPublicTemplateTitle")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
count = result.Count
|
||||
templateTitleList = result.TemplateTitleList
|
||||
return
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package officialaccount
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/silenceper/wechat/v2/officialaccount/draft"
|
||||
"github.com/silenceper/wechat/v2/officialaccount/freepublish"
|
||||
"github.com/silenceper/wechat/v2/officialaccount/ocr"
|
||||
|
||||
"github.com/silenceper/wechat/v2/officialaccount/datacube"
|
||||
@@ -24,7 +26,22 @@ import (
|
||||
|
||||
// OfficialAccount 微信公众号相关API
|
||||
type OfficialAccount struct {
|
||||
ctx *context.Context
|
||||
ctx *context.Context
|
||||
basic *basic.Basic
|
||||
menu *menu.Menu
|
||||
oauth *oauth.Oauth
|
||||
material *material.Material
|
||||
draft *draft.Draft
|
||||
freepublish *freepublish.FreePublish
|
||||
js *js.Js
|
||||
user *user.User
|
||||
templateMsg *message.Template
|
||||
managerMsg *message.Manager
|
||||
device *device.Device
|
||||
broadcast *broadcast.Broadcast
|
||||
datacube *datacube.DataCube
|
||||
ocr *ocr.OCR
|
||||
subscribeMsg *message.Subscribe
|
||||
}
|
||||
|
||||
// NewOfficialAccount 实例化公众号API
|
||||
@@ -49,12 +66,18 @@ func (officialAccount *OfficialAccount) GetContext() *context.Context {
|
||||
|
||||
// GetBasic qr/url 相关配置
|
||||
func (officialAccount *OfficialAccount) GetBasic() *basic.Basic {
|
||||
return basic.NewBasic(officialAccount.ctx)
|
||||
if officialAccount.basic == nil {
|
||||
officialAccount.basic = basic.NewBasic(officialAccount.ctx)
|
||||
}
|
||||
return officialAccount.basic
|
||||
}
|
||||
|
||||
// GetMenu 菜单管理接口
|
||||
func (officialAccount *OfficialAccount) GetMenu() *menu.Menu {
|
||||
return menu.NewMenu(officialAccount.ctx)
|
||||
if officialAccount.menu == nil {
|
||||
officialAccount.menu = menu.NewMenu(officialAccount.ctx)
|
||||
}
|
||||
return officialAccount.menu
|
||||
}
|
||||
|
||||
// GetServer 消息管理:接收事件,被动回复消息管理
|
||||
@@ -72,56 +95,105 @@ func (officialAccount *OfficialAccount) GetAccessToken() (string, error) {
|
||||
|
||||
// GetOauth oauth2网页授权
|
||||
func (officialAccount *OfficialAccount) GetOauth() *oauth.Oauth {
|
||||
return oauth.NewOauth(officialAccount.ctx)
|
||||
if officialAccount.oauth == nil {
|
||||
officialAccount.oauth = oauth.NewOauth(officialAccount.ctx)
|
||||
}
|
||||
return officialAccount.oauth
|
||||
}
|
||||
|
||||
// GetMaterial 素材管理
|
||||
func (officialAccount *OfficialAccount) GetMaterial() *material.Material {
|
||||
return material.NewMaterial(officialAccount.ctx)
|
||||
if officialAccount.material == nil {
|
||||
officialAccount.material = material.NewMaterial(officialAccount.ctx)
|
||||
}
|
||||
return officialAccount.material
|
||||
}
|
||||
|
||||
// GetDraft 草稿箱
|
||||
func (officialAccount *OfficialAccount) GetDraft() *draft.Draft {
|
||||
if officialAccount.draft == nil {
|
||||
officialAccount.draft = draft.NewDraft(officialAccount.ctx)
|
||||
}
|
||||
return officialAccount.draft
|
||||
}
|
||||
|
||||
// GetFreePublish 发布能力
|
||||
func (officialAccount *OfficialAccount) GetFreePublish() *freepublish.FreePublish {
|
||||
if officialAccount.freepublish == nil {
|
||||
officialAccount.freepublish = freepublish.NewFreePublish(officialAccount.ctx)
|
||||
}
|
||||
return officialAccount.freepublish
|
||||
}
|
||||
|
||||
// GetJs js-sdk配置
|
||||
func (officialAccount *OfficialAccount) GetJs() *js.Js {
|
||||
return js.NewJs(officialAccount.ctx)
|
||||
if officialAccount.js == nil {
|
||||
officialAccount.js = js.NewJs(officialAccount.ctx)
|
||||
}
|
||||
return officialAccount.js
|
||||
}
|
||||
|
||||
// GetUser 用户管理接口
|
||||
func (officialAccount *OfficialAccount) GetUser() *user.User {
|
||||
return user.NewUser(officialAccount.ctx)
|
||||
if officialAccount.user == nil {
|
||||
officialAccount.user = user.NewUser(officialAccount.ctx)
|
||||
}
|
||||
return officialAccount.user
|
||||
}
|
||||
|
||||
// GetTemplate 模板消息接口
|
||||
func (officialAccount *OfficialAccount) GetTemplate() *message.Template {
|
||||
return message.NewTemplate(officialAccount.ctx)
|
||||
if officialAccount.templateMsg == nil {
|
||||
officialAccount.templateMsg = message.NewTemplate(officialAccount.ctx)
|
||||
}
|
||||
return officialAccount.templateMsg
|
||||
}
|
||||
|
||||
// GetCustomerMessageManager 客服消息接口
|
||||
func (officialAccount *OfficialAccount) GetCustomerMessageManager() *message.Manager {
|
||||
return message.NewMessageManager(officialAccount.ctx)
|
||||
if officialAccount.managerMsg == nil {
|
||||
officialAccount.managerMsg = message.NewMessageManager(officialAccount.ctx)
|
||||
}
|
||||
return officialAccount.managerMsg
|
||||
}
|
||||
|
||||
// GetDevice 获取智能设备的实例
|
||||
func (officialAccount *OfficialAccount) GetDevice() *device.Device {
|
||||
return device.NewDevice(officialAccount.ctx)
|
||||
if officialAccount.device == nil {
|
||||
officialAccount.device = device.NewDevice(officialAccount.ctx)
|
||||
}
|
||||
return officialAccount.device
|
||||
}
|
||||
|
||||
// GetBroadcast 群发消息
|
||||
// TODO 待完善
|
||||
func (officialAccount *OfficialAccount) GetBroadcast() *broadcast.Broadcast {
|
||||
return broadcast.NewBroadcast(officialAccount.ctx)
|
||||
if officialAccount.broadcast == nil {
|
||||
officialAccount.broadcast = broadcast.NewBroadcast(officialAccount.ctx)
|
||||
}
|
||||
return officialAccount.broadcast
|
||||
}
|
||||
|
||||
// GetDataCube 数据统计
|
||||
func (officialAccount *OfficialAccount) GetDataCube() *datacube.DataCube {
|
||||
return datacube.NewCube(officialAccount.ctx)
|
||||
if officialAccount.datacube == nil {
|
||||
officialAccount.datacube = datacube.NewCube(officialAccount.ctx)
|
||||
}
|
||||
return officialAccount.datacube
|
||||
}
|
||||
|
||||
// GetOCR OCR接口
|
||||
func (officialAccount *OfficialAccount) GetOCR() *ocr.OCR {
|
||||
return ocr.NewOCR(officialAccount.ctx)
|
||||
if officialAccount.ocr == nil {
|
||||
officialAccount.ocr = ocr.NewOCR(officialAccount.ctx)
|
||||
}
|
||||
return officialAccount.ocr
|
||||
}
|
||||
|
||||
// GetSubscribe 公众号订阅消息
|
||||
func (officialAccount *OfficialAccount) GetSubscribe() *message.Subscribe {
|
||||
return message.NewSubscribe(officialAccount.ctx)
|
||||
if officialAccount.subscribeMsg == nil {
|
||||
officialAccount.subscribeMsg = message.NewSubscribe(officialAccount.ctx)
|
||||
}
|
||||
return officialAccount.subscribeMsg
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -9,12 +10,14 @@ import (
|
||||
"reflect"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/silenceper/wechat/v2/officialaccount/context"
|
||||
"github.com/silenceper/wechat/v2/officialaccount/message"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/silenceper/wechat/v2/util"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
// Server struct
|
||||
@@ -34,10 +37,11 @@ type Server struct {
|
||||
ResponseRawXMLMsg []byte
|
||||
ResponseMsg interface{}
|
||||
|
||||
isSafeMode bool
|
||||
random []byte
|
||||
nonce string
|
||||
timestamp int64
|
||||
isSafeMode bool
|
||||
isJSONContent bool
|
||||
random []byte
|
||||
nonce string
|
||||
timestamp int64
|
||||
}
|
||||
|
||||
// NewServer init
|
||||
@@ -69,6 +73,11 @@ func (srv *Server) Serve() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 非安全模式下,请求处理方法返回为nil则直接回复success给微信服务器
|
||||
if response == nil && !srv.isSafeMode {
|
||||
srv.String("success")
|
||||
return nil
|
||||
}
|
||||
|
||||
// debug print request msg
|
||||
log.Debugf("request msg =%s", string(srv.RequestRawXMLMsg))
|
||||
@@ -97,6 +106,10 @@ func (srv *Server) handleRequest() (reply *message.Reply, err error) {
|
||||
srv.isSafeMode = true
|
||||
}
|
||||
|
||||
//set request contentType
|
||||
contentType := srv.Request.Header.Get("Content-Type")
|
||||
srv.isJSONContent = strings.Contains(contentType, "application/json")
|
||||
|
||||
// set openID
|
||||
srv.openID = srv.Query("openid")
|
||||
|
||||
@@ -124,9 +137,9 @@ func (srv *Server) getMessage() (interface{}, error) {
|
||||
var rawXMLMsgBytes []byte
|
||||
var err error
|
||||
if srv.isSafeMode {
|
||||
var encryptedXMLMsg message.EncryptedXMLMsg
|
||||
if err := xml.NewDecoder(srv.Request.Body).Decode(&encryptedXMLMsg); err != nil {
|
||||
return nil, fmt.Errorf("从body中解析xml失败,err=%v", err)
|
||||
encryptedXMLMsg, dataErr := srv.getEncryptBody()
|
||||
if dataErr != nil {
|
||||
return nil, dataErr
|
||||
}
|
||||
|
||||
// 验证消息签名
|
||||
@@ -160,9 +173,48 @@ func (srv *Server) getMessage() (interface{}, error) {
|
||||
return srv.parseRequestMessage(rawXMLMsgBytes)
|
||||
}
|
||||
|
||||
func (srv *Server) getEncryptBody() (*message.EncryptedXMLMsg, error) {
|
||||
var encryptedXMLMsg = &message.EncryptedXMLMsg{}
|
||||
if srv.isJSONContent {
|
||||
if err := json.NewDecoder(srv.Request.Body).Decode(encryptedXMLMsg); err != nil {
|
||||
return nil, fmt.Errorf("从body中解析json失败,err=%v", err)
|
||||
}
|
||||
} else {
|
||||
if err := xml.NewDecoder(srv.Request.Body).Decode(encryptedXMLMsg); err != nil {
|
||||
return nil, fmt.Errorf("从body中解析xml失败,err=%v", err)
|
||||
}
|
||||
}
|
||||
return encryptedXMLMsg, nil
|
||||
}
|
||||
|
||||
func (srv *Server) parseRequestMessage(rawXMLMsgBytes []byte) (msg *message.MixMessage, err error) {
|
||||
msg = &message.MixMessage{}
|
||||
err = xml.Unmarshal(rawXMLMsgBytes, msg)
|
||||
if !srv.isJSONContent {
|
||||
err = xml.Unmarshal(rawXMLMsgBytes, msg)
|
||||
return
|
||||
}
|
||||
//parse json
|
||||
err = json.Unmarshal(rawXMLMsgBytes, msg)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// nonstandard json, 目前小程序订阅消息返回数据格式不标准,订阅消息模板单个List返回是对象,多个List返回是数组。
|
||||
if msg.MsgType == message.MsgTypeEvent {
|
||||
listData := gjson.Get(string(rawXMLMsgBytes), "List")
|
||||
if listData.IsObject() {
|
||||
listItem := message.SubscribeMsgPopupEvent{}
|
||||
if parseErr := json.Unmarshal([]byte(listData.Raw), &listItem); parseErr != nil {
|
||||
return msg, parseErr
|
||||
}
|
||||
msg.SetSubscribeMsgPopupEvents([]message.SubscribeMsgPopupEvent{listItem})
|
||||
} else if listData.IsArray() {
|
||||
listItems := make([]message.SubscribeMsgPopupEvent, 0)
|
||||
if parseErr := json.Unmarshal([]byte(listData.Raw), &listItems); parseErr != nil {
|
||||
return msg, parseErr
|
||||
}
|
||||
msg.SetSubscribeMsgPopupEvents(listItems)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# 微信开放平台
|
||||
|
||||
|
||||
[官方文档](https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/Third_party_platform_appid.html)
|
||||
|
||||
## 快速入门
|
||||
|
||||
### 服务端处理
|
||||
|
||||
```go
|
||||
wc := wechat.NewWechat()
|
||||
memory := cache.NewMemory()
|
||||
@@ -52,7 +52,9 @@ server.Send()
|
||||
|
||||
|
||||
```
|
||||
|
||||
### 待授权处理消息
|
||||
|
||||
```go
|
||||
|
||||
//授权的第三方公众号的appID
|
||||
|
||||
@@ -226,6 +226,36 @@ type AuthorizerInfo struct {
|
||||
}
|
||||
Alias string `json:"alias"`
|
||||
QrcodeURL string `json:"qrcode_url"`
|
||||
|
||||
MiniProgramInfo *MiniProgramInfo `json:"MiniProgramInfo"`
|
||||
RegisterType int `json:"register_type"`
|
||||
AccountStatus int `json:"account_status"`
|
||||
BasicConfig *AuthorizerBasicConfig `json:"basic_config"`
|
||||
}
|
||||
|
||||
// AuthorizerBasicConfig 授权账号的基础配置结构体
|
||||
type AuthorizerBasicConfig struct {
|
||||
IsPhoneConfigured bool `json:"isPhoneConfigured"`
|
||||
IsEmailConfigured bool `json:"isEmailConfigured"`
|
||||
}
|
||||
|
||||
// MiniProgramInfo 授权账号小程序配置 授权账号为小程序时存在
|
||||
type MiniProgramInfo struct {
|
||||
Network struct {
|
||||
RequestDomain []string `json:"RequestDomain"`
|
||||
WsRequestDomain []string `json:"WsRequestDomain"`
|
||||
UploadDomain []string `json:"UploadDomain"`
|
||||
DownloadDomain []string `json:"DownloadDomain"`
|
||||
BizDomain []string `json:"BizDomain"`
|
||||
UDPDomain []string `json:"UDPDomain"`
|
||||
} `json:"network"`
|
||||
Categories []CategoriesInfo `json:"categories"`
|
||||
}
|
||||
|
||||
// CategoriesInfo 授权账号小程序配置的类目信息
|
||||
type CategoriesInfo struct {
|
||||
First string `wx:"first"`
|
||||
Second string `wx:"second"`
|
||||
}
|
||||
|
||||
// GetAuthrInfo 获取授权方的帐号基本信息
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package miniprogram
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
miniContext "github.com/silenceper/wechat/v2/miniprogram/context"
|
||||
"github.com/silenceper/wechat/v2/miniprogram/urllink"
|
||||
openContext "github.com/silenceper/wechat/v2/openplatform/context"
|
||||
"github.com/silenceper/wechat/v2/openplatform/miniprogram/basic"
|
||||
"github.com/silenceper/wechat/v2/openplatform/miniprogram/component"
|
||||
@@ -10,6 +14,30 @@ import (
|
||||
type MiniProgram struct {
|
||||
AppID string
|
||||
openContext *openContext.Context
|
||||
|
||||
authorizerRefreshToken string
|
||||
}
|
||||
|
||||
// GetAccessToken 获取ak
|
||||
func (miniProgram *MiniProgram) GetAccessToken() (string, error) {
|
||||
ak, akErr := miniProgram.openContext.GetAuthrAccessToken(miniProgram.AppID)
|
||||
if akErr == nil {
|
||||
return ak, nil
|
||||
}
|
||||
if miniProgram.authorizerRefreshToken == "" {
|
||||
return "", fmt.Errorf("please set the authorizer_refresh_token first")
|
||||
}
|
||||
akRes, akResErr := miniProgram.GetComponent().RefreshAuthrToken(miniProgram.AppID, miniProgram.authorizerRefreshToken)
|
||||
if akResErr != nil {
|
||||
return "", akResErr
|
||||
}
|
||||
return akRes.AccessToken, nil
|
||||
}
|
||||
|
||||
// SetAuthorizerRefreshToken 设置代执操作业务授权账号authorizer_refresh_token
|
||||
func (miniProgram *MiniProgram) SetAuthorizerRefreshToken(authorizerRefreshToken string) *MiniProgram {
|
||||
miniProgram.authorizerRefreshToken = authorizerRefreshToken
|
||||
return miniProgram
|
||||
}
|
||||
|
||||
// NewMiniProgram 实例化
|
||||
@@ -30,3 +58,10 @@ func (miniProgram *MiniProgram) GetComponent() *component.Component {
|
||||
func (miniProgram *MiniProgram) GetBasic() *basic.Basic {
|
||||
return basic.NewBasic(miniProgram.openContext, miniProgram.AppID)
|
||||
}
|
||||
|
||||
// GetURLLink 小程序URL Link接口 调用前需确认已调用 SetAuthorizerRefreshToken 避免由于缓存中 authorizer_access_token 过期执行中断
|
||||
func (miniProgram *MiniProgram) GetURLLink() *urllink.URLLink {
|
||||
return urllink.NewURLLink(&miniContext.Context{
|
||||
AccessTokenHandle: miniProgram,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -7,8 +7,9 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/structs"
|
||||
"github.com/silenceper/wechat/v2/util"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
"github.com/silenceper/wechat/v2/util"
|
||||
)
|
||||
|
||||
// doc: https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7&index=8
|
||||
|
||||
@@ -48,7 +48,6 @@ type RefundedResp struct {
|
||||
|
||||
// DecryptReqInfo 对退款结果进行解密
|
||||
func (notify *Notify) DecryptReqInfo(result *RefundedResult) (*RefundedReqInfo, error) {
|
||||
var err error
|
||||
if result == nil || result.ReqInfo == nil {
|
||||
return nil, errors.New("empty refunded_result or req_info")
|
||||
}
|
||||
|
||||
@@ -172,7 +172,7 @@ func (o *Order) BridgeConfig(p *Params) (cfg Config, err error) {
|
||||
// BridgeAppConfig get app bridge config
|
||||
func (o *Order) BridgeAppConfig(p *Params) (cfg ConfigForApp, err error) {
|
||||
var (
|
||||
timestamp = strconv.FormatInt(time.Now().Unix(), 10)
|
||||
timestamp = strconv.FormatInt(util.GetCurrTS(), 10)
|
||||
noncestr = util.RandomStr(32)
|
||||
_package = "Sign=WXPay"
|
||||
)
|
||||
@@ -273,7 +273,7 @@ func (o *Order) PrePayOrder(p *Params) (payOrder PreOrder, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// PrePayID will request wechat merchant api and request for a pre payment order id
|
||||
// PrePayID will request wechat merchant api and request for a pre-payment order id
|
||||
func (o *Order) PrePayID(p *Params) (prePayID string, err error) {
|
||||
order, err := o.PrePayOrder(p)
|
||||
if err != nil {
|
||||
|
||||
@@ -23,12 +23,12 @@ func (pay *Pay) GetOrder() *order.Order {
|
||||
return order.NewOrder(pay.cfg)
|
||||
}
|
||||
|
||||
// GetNotify 通知
|
||||
// GetNotify 通知
|
||||
func (pay *Pay) GetNotify() *notify.Notify {
|
||||
return notify.NewNotify(pay.cfg)
|
||||
}
|
||||
|
||||
// GetRefund 退款
|
||||
// GetRefund 退款
|
||||
func (pay *Pay) GetRefund() *refund.Refund {
|
||||
return refund.NewRefund(pay.cfg)
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"github.com/silenceper/wechat/v2/util"
|
||||
)
|
||||
|
||||
// 付款到零钱
|
||||
// walletTransferGateway 付款到零钱
|
||||
// https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_2
|
||||
var walletTransferGateway = "https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers"
|
||||
|
||||
@@ -20,8 +20,7 @@ type Transfer struct {
|
||||
|
||||
// NewTransfer return an instance of Transfer package
|
||||
func NewTransfer(cfg *config.Config) *Transfer {
|
||||
transfer := Transfer{cfg}
|
||||
return &transfer
|
||||
return &Transfer{cfg}
|
||||
}
|
||||
|
||||
// Params 调用参数
|
||||
|
||||
@@ -8,10 +8,15 @@ import (
|
||||
|
||||
// CommonError 微信返回的通用错误json
|
||||
type CommonError struct {
|
||||
apiName string
|
||||
ErrCode int64 `json:"errcode"`
|
||||
ErrMsg string `json:"errmsg"`
|
||||
}
|
||||
|
||||
func (c *CommonError) Error() string {
|
||||
return fmt.Sprintf("%s Error , errcode=%d , errmsg=%s", c.apiName, c.ErrCode, c.ErrMsg)
|
||||
}
|
||||
|
||||
// DecodeWithCommonError 将返回值按照CommonError解析
|
||||
func DecodeWithCommonError(response []byte, apiName string) (err error) {
|
||||
var commError CommonError
|
||||
@@ -19,8 +24,9 @@ func DecodeWithCommonError(response []byte, apiName string) (err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
commError.apiName = apiName
|
||||
if commError.ErrCode != 0 {
|
||||
return fmt.Errorf("%s Error , errcode=%d , errmsg=%s", apiName, commError.ErrCode, commError.ErrMsg)
|
||||
return &commError
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -45,7 +51,11 @@ func DecodeWithError(response []byte, obj interface{}, apiName string) error {
|
||||
return fmt.Errorf("errcode or errmsg is invalid")
|
||||
}
|
||||
if errCode.Int() != 0 {
|
||||
return fmt.Errorf("%s Error , errcode=%d , errmsg=%s", apiName, errCode.Int(), errMsg.String())
|
||||
return &CommonError{
|
||||
apiName: apiName,
|
||||
ErrCode: errCode.Int(),
|
||||
ErrMsg: errMsg.String(),
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
55
util/error_test.go
Normal file
55
util/error_test.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package util
|
||||
|
||||
import "testing"
|
||||
|
||||
var okErrData string = `{"errcode": 0}`
|
||||
var errData string = `{"errcode": 43101, "errmsg": "user refuse to accept the msg"}`
|
||||
var expectError string = "Send Error , errcode=43101 , errmsg=user refuse to accept the msg"
|
||||
|
||||
func TestDecodeWithCommonErrorNoError(t *testing.T) {
|
||||
err := DecodeWithCommonError([]byte(okErrData), "Send")
|
||||
if err != nil {
|
||||
t.Error("DecodeWithCommonError should not return error")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeWithCommonError(t *testing.T) {
|
||||
err := DecodeWithCommonError([]byte(errData), "Send")
|
||||
if err == nil {
|
||||
t.Error("DecodeWithCommonError should return error")
|
||||
return
|
||||
}
|
||||
|
||||
cErr, ok := err.(*CommonError)
|
||||
if !ok {
|
||||
t.Errorf("DecodeWithCommonError should return *CommonError but %T", err)
|
||||
return
|
||||
}
|
||||
if !(cErr.ErrCode == 43101 && cErr.ErrMsg == "user refuse to accept the msg" && cErr.Error() == expectError) {
|
||||
t.Error("DecodeWithCommonError return bad *CommonError")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeWithError(t *testing.T) {
|
||||
type DE struct {
|
||||
CommonError
|
||||
}
|
||||
var obj DE
|
||||
err := DecodeWithError([]byte(errData), &obj, "Send")
|
||||
if err == nil {
|
||||
t.Error("DecodeWithError should return error")
|
||||
return
|
||||
}
|
||||
|
||||
cErr, ok := err.(*CommonError)
|
||||
if !ok {
|
||||
t.Errorf("DecodeWithError should return *CommonError but %T", err)
|
||||
return
|
||||
}
|
||||
if !(cErr.ErrCode == 43101 && cErr.ErrMsg == "user refuse to accept the msg" && cErr.Error() == expectError) {
|
||||
t.Error("DecodeWithError return bad *CommonError")
|
||||
return
|
||||
}
|
||||
}
|
||||
17
util/http.go
17
util/http.go
@@ -43,16 +43,21 @@ func HTTPGetContext(ctx context.Context, uri string) ([]byte, error) {
|
||||
|
||||
// HTTPPost post 请求
|
||||
func HTTPPost(uri string, data string) ([]byte, error) {
|
||||
return HTTPPostContext(context.Background(), uri, data)
|
||||
return HTTPPostContext(context.Background(), uri, []byte(data), nil)
|
||||
}
|
||||
|
||||
// HTTPPostContext post 请求
|
||||
func HTTPPostContext(ctx context.Context, uri string, data string) ([]byte, error) {
|
||||
body := bytes.NewBuffer([]byte(data))
|
||||
func HTTPPostContext(ctx context.Context, uri string, data []byte, header map[string]string) ([]byte, error) {
|
||||
body := bytes.NewBuffer(data)
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodPost, uri, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for key, value := range header {
|
||||
request.Header.Set(key, value)
|
||||
}
|
||||
|
||||
response, err := http.DefaultClient.Do(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -60,7 +65,7 @@ func HTTPPostContext(ctx context.Context, uri string, data string) ([]byte, erro
|
||||
|
||||
defer response.Body.Close()
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("http get error : uri=%v , statusCode=%v", uri, response.StatusCode)
|
||||
return nil, fmt.Errorf("http post error : uri=%v , statusCode=%v", uri, response.StatusCode)
|
||||
}
|
||||
return ioutil.ReadAll(response.Body)
|
||||
}
|
||||
@@ -111,11 +116,11 @@ func PostJSONWithRespContentType(uri string, obj interface{}) ([]byte, string, e
|
||||
}
|
||||
|
||||
// PostFile 上传文件
|
||||
func PostFile(fieldname, filename, uri string) ([]byte, error) {
|
||||
func PostFile(fieldName, filename, uri string) ([]byte, error) {
|
||||
fields := []MultipartFormField{
|
||||
{
|
||||
IsFile: true,
|
||||
Fieldname: fieldname,
|
||||
Fieldname: fieldName,
|
||||
Filename: filename,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package wechat
|
||||
import (
|
||||
"os"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/silenceper/wechat/v2/cache"
|
||||
"github.com/silenceper/wechat/v2/miniprogram"
|
||||
miniConfig "github.com/silenceper/wechat/v2/miniprogram/config"
|
||||
@@ -14,7 +16,6 @@ import (
|
||||
payConfig "github.com/silenceper/wechat/v2/pay/config"
|
||||
"github.com/silenceper/wechat/v2/work"
|
||||
workConfig "github.com/silenceper/wechat/v2/work/config"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -40,8 +41,8 @@ func NewWechat() *Wechat {
|
||||
}
|
||||
|
||||
// SetCache 设置cache
|
||||
func (wc *Wechat) SetCache(cahce cache.Cache) {
|
||||
wc.cache = cahce
|
||||
func (wc *Wechat) SetCache(cache cache.Cache) {
|
||||
wc.cache = cache
|
||||
}
|
||||
|
||||
// GetOfficialAccount 获取微信公众号实例
|
||||
|
||||
3
work/externalcontact/README.md
Normal file
3
work/externalcontact/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
### 企业微信 客户联系部分
|
||||
|
||||
相关文档正在梳理中...
|
||||
17
work/externalcontact/client.go
Normal file
17
work/externalcontact/client.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package externalcontact
|
||||
|
||||
import (
|
||||
"github.com/silenceper/wechat/v2/work/context"
|
||||
)
|
||||
|
||||
// Client 外部联系接口实例
|
||||
type Client struct {
|
||||
*context.Context
|
||||
}
|
||||
|
||||
// NewClient 初始化实例
|
||||
func NewClient(ctx *context.Context) *Client {
|
||||
return &Client{
|
||||
ctx,
|
||||
}
|
||||
}
|
||||
173
work/externalcontact/external_user.go
Normal file
173
work/externalcontact/external_user.go
Normal file
@@ -0,0 +1,173 @@
|
||||
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
|
||||
ExternalUser
|
||||
}
|
||||
|
||||
// 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 []FollowUser `json:"follow_user"`
|
||||
NextCursor string `json:"next_cursor"`
|
||||
}
|
||||
|
||||
// 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 获取外部联系人详情
|
||||
func (r *Client) GetExternalUserDetail(externalUserID string, nextCursor ...string) (*ExternalUser, error) {
|
||||
accessToken, err := r.GetAccessToken()
|
||||
if err != nil {
|
||||
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))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var result ExternalUserDetailResponse
|
||||
err = util.DecodeWithError(response, &result, "get_external_user_detail")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &result.ExternalUser, nil
|
||||
}
|
||||
|
||||
// BatchGetExternalUserDetailsRequest 批量获取外部联系人详情请求
|
||||
type BatchGetExternalUserDetailsRequest struct {
|
||||
UserIDList []string `json:"userid_list"`
|
||||
Cursor string `json:"cursor"`
|
||||
}
|
||||
|
||||
// ExternalUserDetailListResponse 批量获取外部联系人详情响应
|
||||
type ExternalUserDetailListResponse struct {
|
||||
util.CommonError
|
||||
ExternalContactList []ExternalUser `json:"external_contact_list"`
|
||||
}
|
||||
|
||||
// BatchGetExternalUserDetails 批量获取外部联系人详情
|
||||
func (r *Client) BatchGetExternalUserDetails(request BatchGetExternalUserDetailsRequest) ([]ExternalUser, error) {
|
||||
accessToken, err := r.GetAccessToken()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var response []byte
|
||||
jsonData, _ := json.Marshal(request)
|
||||
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 修改客户备注信息
|
||||
func (r *Client) UpdateUserRemark(request UpdateUserRemarkRequest) error {
|
||||
accessToken, err := r.GetAccessToken()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var response []byte
|
||||
jsonData, _ := json.Marshal(request)
|
||||
response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", UpdateUserRemarkURL, accessToken), string(jsonData))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return util.DecodeWithCommonError(response, "UpdateUserRemark")
|
||||
}
|
||||
38
work/externalcontact/follow_user.go
Normal file
38
work/externalcontact/follow_user.go
Normal 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
|
||||
}
|
||||
176
work/externalcontact/statistic.go
Normal file
176
work/externalcontact/statistic.go
Normal 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
|
||||
}
|
||||
188
work/externalcontact/tag.go
Normal file
188
work/externalcontact/tag.go
Normal file
@@ -0,0 +1,188 @@
|
||||
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 string `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, _ := json.Marshal(req)
|
||||
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"`
|
||||
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, _ := json.Marshal(req)
|
||||
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, _ := json.Marshal(req)
|
||||
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, _ := json.Marshal(req)
|
||||
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, _ := json.Marshal(request)
|
||||
response, err = util.HTTPPost(fmt.Sprintf("%s?access_token=%v", MarkCropTagURL, accessToken), string(jsonData))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return util.DecodeWithCommonError(response, "MarkTag")
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -51,7 +51,7 @@ const (
|
||||
SDKApiNotOpen Error = "API 功能没有被开启"
|
||||
)
|
||||
|
||||
//Error 输出错误信息
|
||||
// Error 输出错误信息
|
||||
func (r Error) Error() string {
|
||||
return reflect.ValueOf(r).String()
|
||||
}
|
||||
@@ -85,7 +85,7 @@ func NewSDKErr(code int64, msgList ...string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
//返回未知的自定义错误
|
||||
// 返回未知的自定义错误
|
||||
if len(msgList) > 0 {
|
||||
return Error(strings.Join(msgList, ","))
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
//获取视频号绑定状态
|
||||
// 获取视频号绑定状态
|
||||
corpQualification = "https://qyapi.weixin.qq.com/cgi-bin/kf/get_corp_qualification?access_token=%s"
|
||||
)
|
||||
|
||||
@@ -31,12 +31,10 @@ func (r *Client) GetCorpQualification() (info CorpQualificationSchema, 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(corpQualification, accessToken))
|
||||
if err != nil {
|
||||
if data, err = util.HTTPGet(fmt.Sprintf(corpQualification, accessToken)); err != nil {
|
||||
return info, err
|
||||
}
|
||||
if err = json.Unmarshal(data, &info); err != nil {
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
//发送消息
|
||||
// 发送消息
|
||||
sendMsgAddr = "https://qyapi.weixin.qq.com/cgi-bin/kf/send_msg?access_token=%s"
|
||||
)
|
||||
|
||||
@@ -31,12 +31,10 @@ func (r *Client) SendMsg(options interface{}) (info SendMsgSchema, 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(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 {
|
||||
|
||||
@@ -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 发送文本消息
|
||||
|
||||
@@ -31,19 +31,17 @@ type SendMsgOnEventSchema struct {
|
||||
// 从接待池接入会话,用于发送非工作时间的提示语或超时未回复的提示语等 1条 48小时 文本 事件回调、转接会话接口
|
||||
// 结束会话,用于发送结束会话提示语或满意度评价等 1条 20秒 文本、菜单 事件回调、转接会话接口
|
||||
//
|
||||
//「进入会话事件」响应消息:
|
||||
// 「进入会话事件」响应消息:
|
||||
// 如果满足通过API下发欢迎语条件(条件为:1. 企业没有在管理端配置了原生欢迎语;2. 用户在过去48小时里未收过欢迎语,且未向该用户发过消息),则用户进入会话事件会额外返回一个welcome_code,开发者以此为凭据调用接口(填到该接口code参数),即可向客户发送客服欢迎语。
|
||||
func (r *Client) SendMsgOnEvent(options interface{}) (info SendMsgOnEventSchema, err error) {
|
||||
var (
|
||||
accessToken string
|
||||
data []byte
|
||||
)
|
||||
accessToken, err = r.ctx.GetAccessToken()
|
||||
if err != nil {
|
||||
if accessToken, err = r.ctx.GetAccessToken(); err != nil {
|
||||
return
|
||||
}
|
||||
data, err = util.PostJSON(fmt.Sprintf(sendMsgOnEventAddr, accessToken), options)
|
||||
if err != nil {
|
||||
if data, err = util.PostJSON(fmt.Sprintf(sendMsgOnEventAddr, accessToken), options); err != nil {
|
||||
return
|
||||
}
|
||||
if err = json.Unmarshal(data, &info); err != nil {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -39,12 +39,10 @@ func (r *Client) ServiceStateGet(options ServiceStateGetOptions) (info ServiceSt
|
||||
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 {
|
||||
@@ -76,12 +74,10 @@ func (r *Client) ServiceStateTrans(options ServiceStateTransOptions) (info Servi
|
||||
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 {
|
||||
|
||||
@@ -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{}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 = "参数错误,请求参数错误"
|
||||
|
||||
@@ -33,23 +33,21 @@ func NewOauth(ctx *context.Context) *Oauth {
|
||||
// GetTargetURL 获取授权地址
|
||||
func (ctr *Oauth) GetTargetURL(callbackURL string) string {
|
||||
// url encode
|
||||
urlStr := url.QueryEscape(callbackURL)
|
||||
return fmt.Sprintf(
|
||||
oauthTargetURL,
|
||||
ctr.CorpID,
|
||||
urlStr,
|
||||
url.QueryEscape(callbackURL),
|
||||
)
|
||||
}
|
||||
|
||||
// GetQrContentTargetURL 构造独立窗口登录二维码
|
||||
func (ctr *Oauth) GetQrContentTargetURL(callbackURL string) string {
|
||||
// url encode
|
||||
urlStr := url.QueryEscape(callbackURL)
|
||||
return fmt.Sprintf(
|
||||
oauthQrContentTargetURL,
|
||||
ctr.CorpID,
|
||||
ctr.AgentID,
|
||||
urlStr,
|
||||
url.QueryEscape(callbackURL),
|
||||
util.RandomStr(16),
|
||||
)
|
||||
}
|
||||
@@ -61,21 +59,18 @@ type ResUserInfo struct {
|
||||
UserID string `json:"UserId"`
|
||||
DeviceID string `json:"DeviceId"`
|
||||
// 非企业成员授权时返回
|
||||
OpenID string `json:"OpenId"`
|
||||
OpenID string `json:"OpenId"`
|
||||
ExternalUserID string `json:"external_userid"`
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"github.com/silenceper/wechat/v2/credential"
|
||||
"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/kf"
|
||||
"github.com/silenceper/wechat/v2/work/msgaudit"
|
||||
"github.com/silenceper/wechat/v2/work/oauth"
|
||||
@@ -43,3 +44,8 @@ func (wk *Work) GetMsgAudit() (*msgaudit.Client, error) {
|
||||
func (wk *Work) GetKF() (*kf.Client, error) {
|
||||
return kf.NewClient(wk.ctx.Config)
|
||||
}
|
||||
|
||||
// GetExternalContact get external_contact
|
||||
func (wk *Work) GetExternalContact() *externalcontact.Client {
|
||||
return externalcontact.NewClient(wk.ctx)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user