mirror of
https://github.com/silenceper/wechat.git
synced 2026-02-07 22:22:28 +08:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
39dbfd1c13 | ||
|
|
5ad3475cdb | ||
|
|
bcc41989ed | ||
|
|
e189b87e71 |
29
.github/workflows/release.yml
vendored
29
.github/workflows/release.yml
vendored
@@ -1,29 +0,0 @@
|
|||||||
name: goreleaser
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- '*'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
goreleaser:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
-
|
|
||||||
name: Checkout
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
-
|
|
||||||
name: Set up Go
|
|
||||||
uses: actions/setup-go@v2
|
|
||||||
with:
|
|
||||||
go-version: 1.15
|
|
||||||
-
|
|
||||||
name: Run GoReleaser
|
|
||||||
uses: goreleaser/goreleaser-action@v2
|
|
||||||
with:
|
|
||||||
version: latest
|
|
||||||
args: release --rm-dist
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
# This is an example goreleaser.yaml file with some sane defaults.
|
|
||||||
# Make sure to check the documentation at http://goreleaser.com
|
|
||||||
before:
|
|
||||||
hooks:
|
|
||||||
# You may remove this if you don't use go modules.
|
|
||||||
- go mod download
|
|
||||||
# you may remove this if you don't need go generate
|
|
||||||
- go generate ./...
|
|
||||||
builds:
|
|
||||||
- skip: true
|
|
||||||
|
|
||||||
archives:
|
|
||||||
- replacements:
|
|
||||||
darwin: Darwin
|
|
||||||
linux: Linux
|
|
||||||
windows: Windows
|
|
||||||
386: i386
|
|
||||||
amd64: x86_64
|
|
||||||
|
|
||||||
checksum:
|
|
||||||
name_template: 'checksums.txt'
|
|
||||||
snapshot:
|
|
||||||
name_template: "{{ .Tag }}-next"
|
|
||||||
changelog:
|
|
||||||
sort: asc
|
|
||||||
filters:
|
|
||||||
exclude:
|
|
||||||
- '^docs:'
|
|
||||||
- '^test:'
|
|
||||||
@@ -2,20 +2,16 @@
|
|||||||

|

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

|
|
||||||
|
|
||||||
使用Golang开发的微信SDK,简单、易用。
|
使用Golang开发的微信SDK,简单、易用。
|
||||||
>注意:当前版本为v2版本,v1版本已废弃
|
>当前版本为v2版本
|
||||||
|
|
||||||
|
|
||||||
## 文档 && 例子
|
## 文档 && 例子
|
||||||
[API列表](https://github.com/silenceper/wechat/tree/v2/doc/api)
|
|
||||||
|
|
||||||
[Wechat SDK 2.0 文档](https://silenceper.com/wechat)
|
[Wechat SDK 2.0 文档](https://silenceper.com/wechat)
|
||||||
|
|
||||||
[Wechat SDK 2.0 例子](https://github.com/gowechat/example)
|
[Wechat SDK 2.0 例子](https://github.com/gowechat/example)
|
||||||
|
|
||||||
|
|
||||||
## 快速开始
|
## 快速开始
|
||||||
```
|
```
|
||||||
import "github.com/silenceper/wechat/v2"
|
import "github.com/silenceper/wechat/v2"
|
||||||
@@ -65,10 +61,8 @@ server.Send()
|
|||||||
- openplatform:开放平台API
|
- openplatform:开放平台API
|
||||||
- work:企业微信
|
- work:企业微信
|
||||||
- aispeech:智能对话
|
- aispeech:智能对话
|
||||||
- doc: api文档
|
|
||||||
|
|
||||||
## 贡献
|
## 贡献
|
||||||
- 在[API列表](https://github.com/silenceper/wechat/tree/v2/doc/api)中查看哪些API未实现
|
|
||||||
- 提交issue,描述需要贡献的内容
|
- 提交issue,描述需要贡献的内容
|
||||||
- 完成更改后,提交PR
|
- 完成更改后,提交PR
|
||||||
|
|
||||||
|
|||||||
9
cache/redis.go
vendored
9
cache/redis.go
vendored
@@ -7,7 +7,7 @@ import (
|
|||||||
"github.com/gomodule/redigo/redis"
|
"github.com/gomodule/redigo/redis"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Redis .redis cache
|
//Redis redis cache
|
||||||
type Redis struct {
|
type Redis struct {
|
||||||
conn *redis.Pool
|
conn *redis.Pool
|
||||||
}
|
}
|
||||||
@@ -23,17 +23,16 @@ type RedisOpts struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//NewRedis 实例化
|
//NewRedis 实例化
|
||||||
func NewRedis(opts *RedisOpts, dialOpts ...redis.DialOption) *Redis {
|
func NewRedis(opts *RedisOpts) *Redis {
|
||||||
pool := &redis.Pool{
|
pool := &redis.Pool{
|
||||||
MaxActive: opts.MaxActive,
|
MaxActive: opts.MaxActive,
|
||||||
MaxIdle: opts.MaxIdle,
|
MaxIdle: opts.MaxIdle,
|
||||||
IdleTimeout: time.Second * time.Duration(opts.IdleTimeout),
|
IdleTimeout: time.Second * time.Duration(opts.IdleTimeout),
|
||||||
Dial: func() (redis.Conn, error) {
|
Dial: func() (redis.Conn, error) {
|
||||||
dialOpts = append(dialOpts, []redis.DialOption{
|
return redis.Dial("tcp", opts.Host,
|
||||||
redis.DialDatabase(opts.Database),
|
redis.DialDatabase(opts.Database),
|
||||||
redis.DialPassword(opts.Password),
|
redis.DialPassword(opts.Password),
|
||||||
}...)
|
)
|
||||||
return redis.Dial("tcp", opts.Host, dialOpts...)
|
|
||||||
},
|
},
|
||||||
TestOnBorrow: func(conn redis.Conn, t time.Time) error {
|
TestOnBorrow: func(conn redis.Conn, t time.Time) error {
|
||||||
if time.Since(t) < time.Minute {
|
if time.Since(t) < time.Minute {
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"gopkg.in/h2non/gock.v1"
|
"gopkg.in/h2non/gock.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestGetTicketFromServer .
|
|
||||||
func TestGetTicketFromServer(t *testing.T) {
|
func TestGetTicketFromServer(t *testing.T) {
|
||||||
defer gock.Off()
|
defer gock.Off()
|
||||||
gock.New(getTicketURL).Reply(200).JSON(&ResTicket{Ticket: "mock-ticket", ExpiresIn: 10})
|
gock.New(getTicketURL).Reply(200).JSON(&ResTicket{Ticket: "mock-ticket", ExpiresIn: 10})
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
"github.com/silenceper/wechat/v2/util"
|
"github.com/silenceper/wechat/v2/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// getTicketURL 获取ticket的url
|
//获取ticket的url
|
||||||
const getTicketURL = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=%s&type=jsapi"
|
const getTicketURL = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=%s&type=jsapi"
|
||||||
|
|
||||||
//DefaultJsTicket 默认获取js ticket方法
|
//DefaultJsTicket 默认获取js ticket方法
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
# API 文档
|
|
||||||
已完成以及未完成API列表汇总
|
|
||||||
|
|
||||||
如果有兴趣参与贡献,可以在具体的API表格后面标识自己为贡献者以及完成时间,例如:
|
|
||||||
|
|
||||||
|
|
||||||
| 名称 | 请求方式 | URL | 是否已实现 | 使用方法 |贡献者|完成时间|
|
|
||||||
| :---------------------: | -------- | :------------------------- | ---------- | -------- |-------- |-------- |
|
|
||||||
| 获取公众号类目 | GET | /wxaapi/newtmpl/getcategory | NO | |silenceper| 2021-12-20|
|
|
||||||
|
|
||||||
|
|
||||||
- [微信公众号](./officialaccount.md)
|
|
||||||
- [小程序](./miniprogram.md)
|
|
||||||
- [小游戏](./minigame.md)
|
|
||||||
- [开放平台](./oplatform.md)
|
|
||||||
- [微信支付](./wxpay.md)
|
|
||||||
- [企业微信](./work.md)
|
|
||||||
- [智能对话](./aispeech.md)
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
# 智能对话
|
|
||||||
TODO
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
# 小游戏
|
|
||||||
TODO
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
# 小程序
|
|
||||||
TODO
|
|
||||||
@@ -1,219 +0,0 @@
|
|||||||
# 微信公众号API列表
|
|
||||||
|
|
||||||
## 基础接口
|
|
||||||
|
|
||||||
[官方文档](https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html)
|
|
||||||
|
|
||||||
| 名称 | 请求方式 | URL | 是否已实现 | 使用方法 |
|
|
||||||
| :---------------------: | -------- | :------------------------- | ---------- | -------- |
|
|
||||||
| 获取Access token | GET | /cgi-bin/token | YES | |
|
|
||||||
| 获取微信服务器IP地址 | GET | /cgi-bin/get_api_domain_ip | YES | |
|
|
||||||
| 获取微信callback IP地址 | GET | /cgi-bin/getcallbackip | YES | |
|
|
||||||
| 清理接口调用次数 | POST | /cgi-bin/clear_quota | YES | |
|
|
||||||
|
|
||||||
## 订阅通知
|
|
||||||
|
|
||||||
[官方文档](https://developers.weixin.qq.com/doc/offiaccount/Subscription_Messages/api.html)
|
|
||||||
|
|
||||||
| 名称 | 请求方式 | URL | 是否已实现 | 使用方法 |
|
|
||||||
| -------------------- | -------- | -------------------------------------- | ---------- | ----------------------- |
|
|
||||||
| 选用模板 | 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/gettemplate | YES | (tpl *Subscribe) List() |
|
|
||||||
| 发送订阅通知 | POST | /cgi-bin/message/subscribe/bizsend | YES | (tpl *Subscribe) Send |
|
|
||||||
|
|
||||||
## 客服消息
|
|
||||||
|
|
||||||
### PC 客服能力
|
|
||||||
|
|
||||||
#### 客服管理
|
|
||||||
|
|
||||||
[官方文档](https://developers.weixin.qq.com/doc/offiaccount/Customer_Service/Customer_Service_Management.html)
|
|
||||||
|
|
||||||
| 名称 | 请求方式 | URL | 是否已实现 | 使用方法 |
|
|
||||||
| ---------------- | --------- | -------------------------------------- | ---------- | -------- |
|
|
||||||
| 获取客服基本信息 | GET | /cgi-bin/customservice/getkflist | NO | |
|
|
||||||
| 添加客服帐号 | POST | /customservice/kfaccount/add | NO | |
|
|
||||||
| 邀请绑定客服帐号 | POST | /customservice/kfaccount/inviteworker | NO | |
|
|
||||||
| 设置客服信息 | POST | /customservice/kfaccount/update | NO | |
|
|
||||||
| 上传客服头像 | POST/FORM | /customservice/kfaccount/uploadheadimg | NO | |
|
|
||||||
| 删除客服帐号 | GET | /customservice/kfaccount/del | NO | |
|
|
||||||
|
|
||||||
#### 会话控制
|
|
||||||
|
|
||||||
[官方文档](https://developers.weixin.qq.com/doc/offiaccount/Customer_Service/Session_control.html)
|
|
||||||
|
|
||||||
| 名称 | 请求方式 | URL | 是否已实现 | 使用方法 |
|
|
||||||
| ------------------ | -------- | --------------------------------------- | ---------- | -------- |
|
|
||||||
| 创建会话 | POST | /customservice/kfsession/create | NO | |
|
|
||||||
| 获取客户会话状态 | GET | /customservice/kfsession/getsession | NO | |
|
|
||||||
| 获取客服会话列表 | GET | /customservice/kfsession/getsessionlist | NO | |
|
|
||||||
| 获取未接入会话列表 | POST | /customservice/kfsession/getwaitcase | NO | |
|
|
||||||
|
|
||||||
#### 获取聊天记录
|
|
||||||
|
|
||||||
[官方文档](https://developers.weixin.qq.com/doc/offiaccount/Customer_Service/Obtain_chat_transcript.html)
|
|
||||||
|
|
||||||
| 名称 | 请求方式 | URL | 是否已实现 | 使用方法 |
|
|
||||||
| ------------ | -------- | ----------------------------------- | ---------- | -------- |
|
|
||||||
| 获取聊天记录 | POST | /customservice/msgrecord/getmsglist | NO | |
|
|
||||||
|
|
||||||
### 对话能力
|
|
||||||
|
|
||||||
[官方文档](https://developers.weixin.qq.com/doc/offiaccount/Shopping_Guide/guide.html)
|
|
||||||
|
|
||||||
#### 顾问管理
|
|
||||||
|
|
||||||
| 名称 | 请求方式 | URL | 是否已实现 | 使用方法 |
|
|
||||||
| ------------------------------ | -------- | -------------------------------------- | ---------- | -------- |
|
|
||||||
| 添加顾问 | POST | /cgi-bin/guide/addguideacct | NO | |
|
|
||||||
| 获取顾问信息 | POST | /cgi-bin/guide/getguideacct | NO | |
|
|
||||||
| 修改顾问信息 | POST | /cgi-bin/guide/updateguideacct | NO | |
|
|
||||||
| 删除顾问 | POST | /cgi-bin/guide/delguideacct | NO | |
|
|
||||||
| 获取服务号顾问列表 | POST | /cgi-bin/guide/getguideacctlist | NO | |
|
|
||||||
| 生成顾问二维码 | POST | /cgi-bin/guide/guidecreateqrcode | NO | |
|
|
||||||
| 获取顾问聊天记录 | POST | /cgi-bin/guide/getguidebuyerchatrecord | NO | |
|
|
||||||
| 设置快捷回复与关注自动回复 | POST | /cgi-bin/guide/setguideconfig | NO | |
|
|
||||||
| 获取快捷回复与关注自动回复 | POST | /cgi-bin/guide/getguideconfig | NO | |
|
|
||||||
| 设置敏感词与离线自动回复 | POST | /cgi-bin/guide/setguideacctconfig | NO | |
|
|
||||||
| 获取离线自动回复与敏感词 | POST | /cgi-bin/guide/getguideacctconfig | NO | |
|
|
||||||
| 允许微信用户复制小程序页面路径 | POST | /cgi-bin/guide/pushshowwxapathmenu | NO | |
|
|
||||||
| 新建顾问分组 | POST | /cgi-bin/guide/newguidegroup | NO | |
|
|
||||||
| 获取顾问分组列表 | POST | /cgi-bin/guide/getguidegrouplist | NO | |
|
|
||||||
| 获取顾问分组信息 | POST | /cgi-bin/guide/getgroupinfo | NO | |
|
|
||||||
| 分组内添加顾问 | POST | /cgi-bin/guide/addguide2guidegroup | NO | |
|
|
||||||
| 分组内删除顾问 | POST | /cgi-bin/guide/delguide2guidegroup | NO | |
|
|
||||||
| 获取顾问所在分组 | POST | /cgi-bin/guide/getgroupbyguide | NO | |
|
|
||||||
| 删除指定顾问分组 | POST | /cgi-bin/guide/delguidegroup | NO | |
|
|
||||||
|
|
||||||
#### 客户管理
|
|
||||||
|
|
||||||
| 名称 | 请求方式 | URL | 是否已实现 | 使用方法 |
|
|
||||||
| ------------------------ | -------- | ------------------------------------------- | ---------- | -------- |
|
|
||||||
| 为顾问分配客户 | POST | /cgi-bin/guide/addguidebuyerrelation | NO | |
|
|
||||||
| 为顾问移除客户 | POST | /cgi-bin/guide/delguidebuyerrelation | NO | |
|
|
||||||
| 获取顾问的客户列表 | POST | /cgi-bin/guide/getguidebuyerrelationlist | NO | |
|
|
||||||
| 为客户更换顾问 | POST | /cgi-bin/guide/rebindguideacctforbuyer | NO | |
|
|
||||||
| 修改客户昵称 | POST | /cgi-bin/guide/updateguidebuyerrelation | NO | |
|
|
||||||
| 查询客户所属顾问 | POST | /cgi-bin/guide/getguidebuyerrelationbybuyer | NO | |
|
|
||||||
| 查询指定顾问和客户的关系 | POST | /cgi-bin/guide/getguidebuyerrelation | NO | |
|
|
||||||
|
|
||||||
#### 标签管理
|
|
||||||
|
|
||||||
| 名称 | 请求方式 | URL | 是否已实现 | 使用方法 |
|
|
||||||
| ------------------ | -------- | -------------------------------------- | ---------- | -------- |
|
|
||||||
| 新建标签类型 | POST | /cgi-bin/guide/newguidetagoption | NO | |
|
|
||||||
| 删除标签类型 | POST | /cgi-bin/guide/delguidetagoption | NO | |
|
|
||||||
| 为标签添加可选值 | POST | /cgi-bin/guide/addguidetagoption | NO | |
|
|
||||||
| 获取标签和可选值 | POST | /cgi-bin/guide/getguidetagoption | NO | |
|
|
||||||
| 为客户设置标签 | POST | /cgi-bin/guide/addguidebuyertag | NO | |
|
|
||||||
| 查询客户标签 | POST | /cgi-bin/guide/getguidebuyertag | NO | |
|
|
||||||
| 根据标签值筛选客户 | POST | /cgi-bin/guide/queryguidebuyerbytag | NO | |
|
|
||||||
| 删除客户标签 | POST | /cgi-bin/guide/delguidebuyertag | NO | |
|
|
||||||
| 设置自定义客户信息 | POST | /cgi-bin/guide/addguidebuyerdisplaytag | NO | |
|
|
||||||
| 获取自定义客户信息 | POST | /cgi-bin/guide/getguidebuyerdisplaytag | NO | |
|
|
||||||
|
|
||||||
#### 素材管理
|
|
||||||
|
|
||||||
| 名称 | 请求方式 | URL | 是否已实现 | 使用方法 |
|
|
||||||
| ------------------ | -------- | ------------------------------------ | ---------- | -------- |
|
|
||||||
| 添加小程序卡片素材 | POST | /cgi-bin/guide/setguidecardmaterial | NO | |
|
|
||||||
| 查询小程序卡片素材 | POST | /cgi-bin/guide/getguidecardmaterial | NO | |
|
|
||||||
| 删除小程序卡片素材 | POST | /cgi-bin/guide/delguidecardmaterial | NO | |
|
|
||||||
| 添加图片素材 | POST | /cgi-bin/guide/setguideimagematerial | NO | |
|
|
||||||
| 查询图片素材 | POST | /cgi-bin/guide/getguideimagematerial | NO | |
|
|
||||||
| 删除图片素材 | POST | /cgi-bin/guide/delguideimagematerial | NO | |
|
|
||||||
| 添加文字素材 | POST | /cgi-bin/guide/setguidewordmaterial | NO | |
|
|
||||||
| 查询文字素材 | POST | /cgi-bin/guide/getguidewordmaterial | NO | |
|
|
||||||
| 删除文字素材 | POST | /cgi-bin/guide/delguidewordmaterial | NO | |
|
|
||||||
|
|
||||||
#### 群发任务管理
|
|
||||||
|
|
||||||
[官方文档](https://developers.weixin.qq.com/doc/offiaccount/Shopping_Guide/task-account/shopping-guide.addGuideMassendJob.html)
|
|
||||||
|
|
||||||
| 名称 | 请求方式 | URL | 是否已实现 | 使用方法 |
|
|
||||||
| -------------------- | -------- | ------------------------------------- | ---------- | -------- |
|
|
||||||
| 添加群发任务 | POST | /cgi-bin/guide/addguidemassendjob | NO | |
|
|
||||||
| 获取群发任务列表 | POST | /cgi-bin/guide/getguidemassendjoblist | NO | |
|
|
||||||
| 获取指定群发任务信息 | POST | /cgi-bin/guide/getguidemassendjob | NO | |
|
|
||||||
| 修改群发任务 | POST | /cgi-bin/guide/updateguidemassendjob | NO | |
|
|
||||||
| 取消群发任务 | POST | /cgi-bin/guide/cancelguidemassendjob | NO | |
|
|
||||||
|
|
||||||
## 微信网页开发
|
|
||||||
|
|
||||||
[官方文档](https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html)
|
|
||||||
|
|
||||||
| 名称 | 请求方式 | URL | 是否已实现 | 使用方法 |
|
|
||||||
| ------------------------------------------------------------ | -------- | --------------------------------------------------- | ---------- | ----------------------------------- |
|
|
||||||
| 获取跳转的url地址 | GET | https://open.weixin.qq.com/connect/oauth2/authorize | YES | (oauth *Oauth) GetRedirectURL |
|
|
||||||
| 获取网页应用跳转的url地址 | GET | https://open.weixin.qq.com/connect/qrconnect | YES | (oauth *Oauth) GetWebAppRedirectURL |
|
|
||||||
| 通过网页授权的code 换取access_token(区别于context中的access_token) | GET | /sns/oauth2/access_token | YES | (oauth *Oauth) GetUserAccessToken |
|
|
||||||
| 刷新access_token | GET | /sns/oauth2/refresh_token? | YES | (oauth *Oauth) RefreshAccessToken |
|
|
||||||
| 检验access_token是否有效 | GET | /sns/auth | YES | (oauth *Oauth) CheckAccessToken( |
|
|
||||||
| 拉取用户信息(需scope为 snsapi_userinfo) | GET | /sns/userinfo | YES | (oauth *Oauth) GetUserInfo |
|
|
||||||
| 获取jssdk需要的配置参数 | GET | /cgi-bin/ticket/getticket | YES | (js *Js) GetConfig |
|
|
||||||
|
|
||||||
## 素材管理
|
|
||||||
|
|
||||||
## 草稿箱
|
|
||||||
|
|
||||||
[官方文档](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 |
|
|
||||||
|
|
||||||
|
|
||||||
## 图文消息留言管理
|
|
||||||
|
|
||||||
## 用户管理
|
|
||||||
|
|
||||||
## 账号管理
|
|
||||||
|
|
||||||
## 数据统计
|
|
||||||
|
|
||||||
## 微信卡券
|
|
||||||
|
|
||||||
## 微信门店
|
|
||||||
|
|
||||||
## 智能接口
|
|
||||||
|
|
||||||
## 微信设备功能
|
|
||||||
|
|
||||||
## 微信“一物一码”
|
|
||||||
|
|
||||||
## 微信发票
|
|
||||||
|
|
||||||
## 微信非税缴费
|
|
||||||
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
# 开放平台
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
# 企业微信
|
|
||||||
|
|
||||||
host: https://qyapi.weixin.qq.com/
|
|
||||||
|
|
||||||
## 微信客服
|
|
||||||
|
|
||||||
[官方文档](https://work.weixin.qq.com/api/doc/90000/90135/94638)
|
|
||||||
|
|
||||||
### 客服账号管理
|
|
||||||
|
|
||||||
[官方文档](https://open.work.weixin.qq.com/api/doc/90001/90143/94684)
|
|
||||||
|
|
||||||
| 名称 | 请求方式 | URL | 是否已实现 | 使用方法 |贡献者 |
|
|
||||||
| :--------------: | -------- | :-------------------------- | ---------- | -------------------------------|------------|
|
|
||||||
| 添加客服帐号 | POST | /cgi-bin/kf/account/add | YES | (r *Client) AccountAdd | NICEXAI |
|
|
||||||
| 删除客服帐号 | POST | /cgi-bin/kf/account/del | YES | (r *Client) AccountDel | NICEXAI |
|
|
||||||
| 修改客服帐号 | POST | /cgi-bin/kf/account/update | YES | (r *Client) AccountUpdate | NICEXAI |
|
|
||||||
| 获取客服帐号列表 | GET | /cgi-bin/kf/account/list | YES | (r *Client) AccountList | NICEXAI |
|
|
||||||
| 获取客服帐号链接 | GET | /cgi-bin/kf/add_contact_way | YES | (r *Client) AddContactWay | NICEXAI |
|
|
||||||
|
|
||||||
### 接待人员列表
|
|
||||||
|
|
||||||
[官方文档](https://open.work.weixin.qq.com/api/doc/90001/90143/94693)
|
|
||||||
|
|
||||||
| 名称 | 请求方式 | URL | 是否已实现 | 使用方法 |贡献者 |
|
|
||||||
| :--------------: | -------- | :-------------------------- | ---------- | -------------------------------|------------|
|
|
||||||
| 添加接待人员 | POST | /cgi-bin/kf/servicer/add | YES | (r *Client) ReceptionistAdd | NICEXAI |
|
|
||||||
| 删除接待人员 | POST | /cgi-bin/kf/servicer/del | YES | (r *Client) ReceptionistDel | NICEXAI |
|
|
||||||
| 获取接待人员列表 | GET | /cgi-bin/kf/servicer/list | YES | (r *Client) ReceptionistList | NICEXAI |
|
|
||||||
|
|
||||||
### 会话分配与消息收发
|
|
||||||
|
|
||||||
[官方文档](https://open.work.weixin.qq.com/api/doc/90001/90143/94694)
|
|
||||||
|
|
||||||
| 名称 | 请求方式 | URL | 是否已实现 | 使用方法 |贡献者 |
|
|
||||||
| :--------------: | -------- | :-------------------------------| ---------- | ------------------------------- |------------|
|
|
||||||
| 获取会话状态 | POST | /cgi-bin/kf/service_state/get | YES | (r *Client) ServiceStateGet | NICEXAI |
|
|
||||||
| 变更会话状态 | POST | /cgi-bin/kf/service_state/trans | YES | (r *Client) ServiceStateTrans | NICEXAI |
|
|
||||||
| 读取消息 | POST | /cgi-bin/kf/sync_msg | YES | (r *Client) SyncMsg | NICEXAI |
|
|
||||||
| 发送消息 | POST | /cgi-bin/kf/send_msg | YES | (r *Client) SendMsg | NICEXAI |
|
|
||||||
| 发送事件响应消息 | POST | /cgi-bin/kf/send_msg_on_event | YES | (r *Client) SendMsgOnEvent | NICEXAI |
|
|
||||||
|
|
||||||
### 升级服务配置
|
|
||||||
|
|
||||||
[官方文档](https://open.work.weixin.qq.com/api/doc/90001/90143/94702)
|
|
||||||
|
|
||||||
| 名称 | 请求方式 | URL | 是否已实现 | 使用方法 |贡献者 |
|
|
||||||
| :--------------: | -------- | :-------------------------------------------------| ---------- | ------------------------------- |------------|
|
|
||||||
| 获取配置的专员与客户群 | POST | /cgi-bin/kf/customer/get_upgrade_service_config | YES | (r *Client) UpgradeServiceConfig | NICEXAI |
|
|
||||||
| 为客户升级为专员或客户群服务 | POST | /cgi-bin/kf/customer/upgrade_service | YES | (r *Client) UpgradeService | NICEXAI |
|
|
||||||
| 为客户取消推荐 | POST | /cgi-bin/kf/customer/cancel_upgrade_service | YES | (r *Client) UpgradeServiceCancel | NICEXAI |
|
|
||||||
|
|
||||||
### 其他基础信息获取
|
|
||||||
|
|
||||||
[官方文档](https://open.work.weixin.qq.com/api/doc/90001/90143/95148)
|
|
||||||
|
|
||||||
| 名称 | 请求方式 | URL | 是否已实现 | 使用方法 | 贡献者 |
|
|
||||||
| :--------------: | -------- | :---------------------------------------| ---------- | ------------------------------- |------------|
|
|
||||||
| 获取客户基础信息 | POST | /cgi-bin/kf/customer/batchget | YES | (r *Client) CustomerBatchGet | NICEXAI |
|
|
||||||
| 获取视频号绑定状态 | GET | /cgi-bin/kf/get_corp_qualification | YES | (r *Client) GetCorpQualification | NICEXAI |
|
|
||||||
|
|
||||||
## 应用管理
|
|
||||||
|
|
||||||
TODO
|
|
||||||
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
# 微信支付
|
|
||||||
TODO
|
|
||||||
2
go.mod
2
go.mod
@@ -5,7 +5,7 @@ go 1.14
|
|||||||
require (
|
require (
|
||||||
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b
|
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b
|
||||||
github.com/fatih/structs v1.1.0
|
github.com/fatih/structs v1.1.0
|
||||||
github.com/gomodule/redigo v1.8.5
|
github.com/gomodule/redigo v1.8.4
|
||||||
github.com/kr/text v0.2.0 // indirect
|
github.com/kr/text v0.2.0 // indirect
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||||
github.com/sirupsen/logrus v1.8.1
|
github.com/sirupsen/logrus v1.8.1
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -6,8 +6,8 @@ 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/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
||||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
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.4 h1:Z5JUg94HMTR1XpwBaSH4vq3+PNSIykBLxMdglbw10gg=
|
||||||
github.com/gomodule/redigo v1.8.5/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0=
|
github.com/gomodule/redigo v1.8.4/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0=
|
||||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
|
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/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/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package auth
|
package auth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
context2 "context"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
@@ -11,10 +10,6 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
code2SessionURL = "https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code"
|
code2SessionURL = "https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code"
|
||||||
|
|
||||||
checkEncryptedDataURL = "https://api.weixin.qq.com/wxa/business/checkencryptedmsg?access_token=%s"
|
|
||||||
|
|
||||||
getPhoneNumber = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=%s"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
//Auth 登录/用户信息
|
//Auth 登录/用户信息
|
||||||
@@ -36,26 +31,16 @@ type ResCode2Session struct {
|
|||||||
UnionID string `json:"unionid"` // 用户在开放平台的唯一标识符,在满足UnionID下发条件的情况下会返回
|
UnionID string `json:"unionid"` // 用户在开放平台的唯一标识符,在满足UnionID下发条件的情况下会返回
|
||||||
}
|
}
|
||||||
|
|
||||||
// RspCheckEncryptedData .
|
|
||||||
type RspCheckEncryptedData struct {
|
|
||||||
util.CommonError
|
|
||||||
|
|
||||||
Vaild bool `json:"vaild"` // 是否是合法的数据
|
|
||||||
CreateTime uint `json:"create_time"` // 加密数据生成的时间戳
|
|
||||||
}
|
|
||||||
|
|
||||||
//Code2Session 登录凭证校验。
|
//Code2Session 登录凭证校验。
|
||||||
func (auth *Auth) Code2Session(jsCode string) (result ResCode2Session, err error) {
|
func (auth *Auth) Code2Session(jsCode string) (result ResCode2Session, err error) {
|
||||||
return auth.Code2SessionContext(context2.Background(), jsCode)
|
urlStr := fmt.Sprintf(code2SessionURL, auth.AppID, auth.AppSecret, jsCode)
|
||||||
}
|
|
||||||
|
|
||||||
// Code2SessionContext 登录凭证校验。
|
|
||||||
func (auth *Auth) Code2SessionContext(ctx context2.Context, jsCode string) (result ResCode2Session, err error) {
|
|
||||||
var response []byte
|
var response []byte
|
||||||
if response, err = util.HTTPGetContext(ctx, fmt.Sprintf(code2SessionURL, auth.AppID, auth.AppSecret, jsCode)); err != nil {
|
response, err = util.HTTPGet(urlStr)
|
||||||
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = json.Unmarshal(response, &result); err != nil {
|
err = json.Unmarshal(response, &result)
|
||||||
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if result.ErrCode != 0 {
|
if result.ErrCode != 0 {
|
||||||
@@ -69,65 +54,3 @@ func (auth *Auth) Code2SessionContext(ctx context2.Context, jsCode string) (resu
|
|||||||
func (auth *Auth) GetPaidUnionID() {
|
func (auth *Auth) GetPaidUnionID() {
|
||||||
//TODO
|
//TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckEncryptedData .检查加密信息是否由微信生成(当前只支持手机号加密数据),只能检测最近3天生成的加密数据
|
|
||||||
func (auth *Auth) CheckEncryptedData(encryptedMsgHash string) (result RspCheckEncryptedData, err error) {
|
|
||||||
return auth.CheckEncryptedDataContext(context2.Background(), encryptedMsgHash)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckEncryptedDataContext .检查加密信息是否由微信生成(当前只支持手机号加密数据),只能检测最近3天生成的加密数据
|
|
||||||
func (auth *Auth) CheckEncryptedDataContext(ctx context2.Context, encryptedMsgHash string) (result RspCheckEncryptedData, err error) {
|
|
||||||
var response []byte
|
|
||||||
var (
|
|
||||||
at string
|
|
||||||
)
|
|
||||||
if at, err = auth.GetAccessToken(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if response, err = util.HTTPPostContext(ctx, fmt.Sprintf(checkEncryptedDataURL, at), "encrypted_msg_hash="+encryptedMsgHash); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err = util.DecodeWithError(response, &result, "CheckEncryptedDataAuth"); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPhoneNumberResponse 新版获取用户手机号响应结构体
|
|
||||||
type GetPhoneNumberResponse struct {
|
|
||||||
util.CommonError
|
|
||||||
|
|
||||||
PhoneInfo PhoneInfo `json:"phone_info"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// PhoneInfo 获取用户手机号内容
|
|
||||||
type PhoneInfo struct {
|
|
||||||
PhoneNumber string `json:"phoneNumber"` // 用户绑定的手机号
|
|
||||||
PurePhoneNumber string `json:"purePhoneNumber"` // 没有区号的手机号
|
|
||||||
CountryCode string `json:"countryCode"` // 区号
|
|
||||||
WaterMark struct {
|
|
||||||
Timestamp int64 `json:"timestamp"`
|
|
||||||
AppID string `json:"appid"`
|
|
||||||
} `json:"watermark"` // 数据水印
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPhoneNumber 小程序通过code获取用户手机号
|
|
||||||
func (auth *Auth) GetPhoneNumber(code string) (result GetPhoneNumberResponse, err error) {
|
|
||||||
var response []byte
|
|
||||||
var (
|
|
||||||
at string
|
|
||||||
)
|
|
||||||
if at, err = auth.GetAccessToken(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
body := map[string]interface{}{
|
|
||||||
"code": code,
|
|
||||||
}
|
|
||||||
if response, err = util.PostJSON(fmt.Sprintf(getPhoneNumber, at), body); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err = util.DecodeWithError(response, &result, "phonenumber.getPhoneNumber"); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"github.com/silenceper/wechat/v2/cache"
|
"github.com/silenceper/wechat/v2/cache"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config .config for 小程序
|
// Config config for 小程序
|
||||||
type Config struct {
|
type Config struct {
|
||||||
AppID string `json:"app_id"` // appid
|
AppID string `json:"app_id"` // appid
|
||||||
AppSecret string `json:"app_secret"` // appsecret
|
AppSecret string `json:"app_secret"` // appsecret
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/silenceper/wechat/v2/miniprogram/context"
|
"github.com/silenceper/wechat/v2/miniprogram/context"
|
||||||
)
|
)
|
||||||
@@ -91,9 +90,6 @@ func GetCipherText(sessionKey, encryptedData, iv string) ([]byte, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(ivBytes) != aes.BlockSize {
|
|
||||||
return nil, fmt.Errorf("bad iv length %d", len(ivBytes))
|
|
||||||
}
|
|
||||||
block, err := aes.NewCipher(aesKey)
|
block, err := aes.NewCipher(aesKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
package encryptor
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/base64"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGetCipherText_BadIV(t *testing.T) {
|
|
||||||
keyData := base64.StdEncoding.EncodeToString([]byte("1234567890123456"))
|
|
||||||
badData := base64.StdEncoding.EncodeToString([]byte("1"))
|
|
||||||
_, err := GetCipherText(keyData, badData, badData)
|
|
||||||
assert.Error(t, err)
|
|
||||||
}
|
|
||||||
@@ -9,12 +9,9 @@ import (
|
|||||||
"github.com/silenceper/wechat/v2/miniprogram/context"
|
"github.com/silenceper/wechat/v2/miniprogram/context"
|
||||||
"github.com/silenceper/wechat/v2/miniprogram/encryptor"
|
"github.com/silenceper/wechat/v2/miniprogram/encryptor"
|
||||||
"github.com/silenceper/wechat/v2/miniprogram/message"
|
"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/qrcode"
|
||||||
"github.com/silenceper/wechat/v2/miniprogram/shortlink"
|
|
||||||
"github.com/silenceper/wechat/v2/miniprogram/subscribe"
|
"github.com/silenceper/wechat/v2/miniprogram/subscribe"
|
||||||
"github.com/silenceper/wechat/v2/miniprogram/tcb"
|
"github.com/silenceper/wechat/v2/miniprogram/tcb"
|
||||||
"github.com/silenceper/wechat/v2/miniprogram/urllink"
|
|
||||||
"github.com/silenceper/wechat/v2/miniprogram/werun"
|
"github.com/silenceper/wechat/v2/miniprogram/werun"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -58,11 +55,6 @@ func (miniProgram *MiniProgram) GetAnalysis() *analysis.Analysis {
|
|||||||
return analysis.NewAnalysis(miniProgram.ctx)
|
return analysis.NewAnalysis(miniProgram.ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPrivacy 小程序隐私协议相关API
|
|
||||||
func (miniProgram *MiniProgram) GetPrivacy() *privacy.Privacy {
|
|
||||||
return privacy.NewPrivacy(miniProgram.ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
//GetQRCode 小程序码相关API
|
//GetQRCode 小程序码相关API
|
||||||
func (miniProgram *MiniProgram) GetQRCode() *qrcode.QRCode {
|
func (miniProgram *MiniProgram) GetQRCode() *qrcode.QRCode {
|
||||||
return qrcode.NewQRCode(miniProgram.ctx)
|
return qrcode.NewQRCode(miniProgram.ctx)
|
||||||
@@ -92,13 +84,3 @@ func (miniProgram *MiniProgram) GetWeRun() *werun.WeRun {
|
|||||||
func (miniProgram *MiniProgram) GetContentSecurity() *content.Content {
|
func (miniProgram *MiniProgram) GetContentSecurity() *content.Content {
|
||||||
return content.NewContent(miniProgram.ctx)
|
return content.NewContent(miniProgram.ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetURLLink 小程序URL Link接口
|
|
||||||
func (miniProgram *MiniProgram) GetURLLink() *urllink.URLLink {
|
|
||||||
return urllink.NewURLLink(miniProgram.ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetShortLink 小程序短链接口
|
|
||||||
func (miniProgram *MiniProgram) GetShortLink() *shortlink.ShortLink {
|
|
||||||
return shortlink.NewShortLink(miniProgram.ctx)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,167 +0,0 @@
|
|||||||
package privacy
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/silenceper/wechat/v2/miniprogram/context"
|
|
||||||
"github.com/silenceper/wechat/v2/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Privacy 小程序授权隐私设置
|
|
||||||
type Privacy struct {
|
|
||||||
*context.Context
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPrivacy 实例化小程序隐私接口
|
|
||||||
// 文档地址 https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/api/privacy_config/set_privacy_setting.html
|
|
||||||
func NewPrivacy(context *context.Context) *Privacy {
|
|
||||||
if context == nil {
|
|
||||||
panic("NewPrivacy got a nil context")
|
|
||||||
}
|
|
||||||
return &Privacy{
|
|
||||||
context,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// OwnerSetting 收集方(开发者)信息配置
|
|
||||||
type OwnerSetting struct {
|
|
||||||
ContactEmail string `json:"contact_email"`
|
|
||||||
ContactPhone string `json:"contact_phone"`
|
|
||||||
ContactQQ string `json:"contact_qq"`
|
|
||||||
ContactWeixin string `json:"contact_weixin"`
|
|
||||||
ExtFileMediaID string `json:"ext_file_media_id"`
|
|
||||||
NoticeMethod string `json:"notice_method"`
|
|
||||||
StoreExpireTimestamp string `json:"store_expire_timestamp"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// SettingItem 收集权限的配置
|
|
||||||
type SettingItem struct {
|
|
||||||
PrivacyKey string `json:"privacy_key"`
|
|
||||||
PrivacyText string `json:"privacy_text"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetPrivacySettingRequest 设置权限的请求参数
|
|
||||||
type SetPrivacySettingRequest struct {
|
|
||||||
PrivacyVer int `json:"privacy_ver"`
|
|
||||||
OwnerSetting OwnerSetting `json:"owner_setting"`
|
|
||||||
SettingList []SettingItem `json:"setting_list"`
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
setPrivacySettingURL = "https://api.weixin.qq.com/cgi-bin/component/setprivacysetting"
|
|
||||||
getPrivacySettingURL = "https://api.weixin.qq.com/cgi-bin/component/getprivacysetting"
|
|
||||||
uploadPrivacyExtFileURL = "https://api.weixin.qq.com/cgi-bin/component/uploadprivacyextfile"
|
|
||||||
|
|
||||||
// PrivacyV1 用户隐私保护指引的版本,1表示现网版本。
|
|
||||||
PrivacyV1 = 1
|
|
||||||
// PrivacyV2 2表示开发版。默认是2开发版。
|
|
||||||
PrivacyV2 = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetPrivacySettingResponse 获取权限配置的响应结果
|
|
||||||
type GetPrivacySettingResponse struct {
|
|
||||||
util.CommonError
|
|
||||||
CodeExist int `json:"code_exist"`
|
|
||||||
PrivacyList []string `json:"privacy_list"`
|
|
||||||
SettingList []SettingResponseItem `json:"setting_list"`
|
|
||||||
UpdateTime int64 `json:"update_time"`
|
|
||||||
OwnerSetting OwnerSetting `json:"owner_setting"`
|
|
||||||
PrivacyDesc DescList `json:"privacy_desc"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// SettingResponseItem 获取权限设置的响应明细
|
|
||||||
type SettingResponseItem struct {
|
|
||||||
PrivacyKey string `json:"privacy_key"`
|
|
||||||
PrivacyText string `json:"privacy_text"`
|
|
||||||
PrivacyLabel string `json:"privacy_label"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// DescList 权限列表(保持与官方一致)
|
|
||||||
type DescList struct {
|
|
||||||
PrivacyDescList []Desc `json:"privacy_desc_list"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Desc 权限列表明细(保持与官方一致)
|
|
||||||
type Desc struct {
|
|
||||||
PrivacyDesc string `json:"privacy_desc"`
|
|
||||||
PrivacyKey string `json:"privacy_key"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPrivacySetting 获取小程序权限配置
|
|
||||||
func (s *Privacy) GetPrivacySetting(privacyVer int) (GetPrivacySettingResponse, error) {
|
|
||||||
accessToken, err := s.GetAccessToken()
|
|
||||||
if err != nil {
|
|
||||||
return GetPrivacySettingResponse{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
response, err := util.PostJSON(fmt.Sprintf("%s?access_token=%s", getPrivacySettingURL, accessToken), map[string]int{
|
|
||||||
"privacy_ver": privacyVer,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return GetPrivacySettingResponse{}, err
|
|
||||||
}
|
|
||||||
// 返回错误信息
|
|
||||||
var result GetPrivacySettingResponse
|
|
||||||
if err = util.DecodeWithError(response, &result, "getprivacysetting"); err != nil {
|
|
||||||
return GetPrivacySettingResponse{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetPrivacySetting 更新小程序权限配置
|
|
||||||
func (s *Privacy) SetPrivacySetting(privacyVer int, ownerSetting OwnerSetting, settingList []SettingItem) error {
|
|
||||||
if privacyVer == PrivacyV1 && len(settingList) > 0 {
|
|
||||||
return errors.New("当privacy_ver传2或者不传时,setting_list是必填;当privacy_ver传1时,该参数不可传")
|
|
||||||
}
|
|
||||||
accessToken, err := s.GetAccessToken()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
response, err := util.PostJSON(fmt.Sprintf("%s?access_token=%s", setPrivacySettingURL, accessToken), SetPrivacySettingRequest{
|
|
||||||
PrivacyVer: privacyVer,
|
|
||||||
OwnerSetting: ownerSetting,
|
|
||||||
SettingList: settingList,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 返回错误信息
|
|
||||||
if err = util.DecodeWithCommonError(response, "setprivacysetting"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// UploadPrivacyExtFileResponse 上传权限定义模板响应参数
|
|
||||||
type UploadPrivacyExtFileResponse struct {
|
|
||||||
util.CommonError
|
|
||||||
ExtFileMediaID string `json:"ext_file_media_id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// UploadPrivacyExtFile 上传权限定义模板
|
|
||||||
func (s *Privacy) UploadPrivacyExtFile(fileData []byte) (UploadPrivacyExtFileResponse, error) {
|
|
||||||
accessToken, err := s.GetAccessToken()
|
|
||||||
if err != nil {
|
|
||||||
return UploadPrivacyExtFileResponse{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
response, err := util.PostJSON(fmt.Sprintf("%s?access_token=%s", uploadPrivacyExtFileURL, accessToken), map[string][]byte{
|
|
||||||
"file": fileData,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return UploadPrivacyExtFileResponse{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 返回错误信息
|
|
||||||
var result UploadPrivacyExtFileResponse
|
|
||||||
if err = util.DecodeWithError(response, &result, "setprivacysetting"); err != nil {
|
|
||||||
return UploadPrivacyExtFileResponse{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
@@ -40,8 +40,6 @@ type QRCoder struct {
|
|||||||
Page string `json:"page,omitempty"`
|
Page string `json:"page,omitempty"`
|
||||||
// path 扫码进入的小程序页面路径
|
// path 扫码进入的小程序页面路径
|
||||||
Path string `json:"path,omitempty"`
|
Path string `json:"path,omitempty"`
|
||||||
// checkPath 检查page 是否存在,为 true 时 page 必须是已经发布的小程序存在的页面(否则报错);为 false 时允许小程序未发布或者 page 不存在, 但page 有数量上限(60000个)请勿滥用
|
|
||||||
CheckPath bool `json:"check_path,omitempty"`
|
|
||||||
// width 图片宽度
|
// width 图片宽度
|
||||||
Width int `json:"width,omitempty"`
|
Width int `json:"width,omitempty"`
|
||||||
// scene 最大32个可见字符,只支持数字,大小写英文以及部分特殊字符:!#$&'()*+,/:;=?@-._~,其它字符请自行编码为合法字符(因不支持%,中文无法使用 urlencode 处理,请使用其他编码方式)
|
// scene 最大32个可见字符,只支持数字,大小写英文以及部分特殊字符:!#$&'()*+,/:;=?@-._~,其它字符请自行编码为合法字符(因不支持%,中文无法使用 urlencode 处理,请使用其他编码方式)
|
||||||
@@ -52,8 +50,6 @@ type QRCoder struct {
|
|||||||
LineColor Color `json:"line_color,omitempty"`
|
LineColor Color `json:"line_color,omitempty"`
|
||||||
// isHyaline 是否需要透明底色
|
// isHyaline 是否需要透明底色
|
||||||
IsHyaline bool `json:"is_hyaline,omitempty"`
|
IsHyaline bool `json:"is_hyaline,omitempty"`
|
||||||
// envVersion 要打开的小程序版本。正式版为 "release",体验版为 "trial",开发版为 "develop"
|
|
||||||
EnvVersion string `json:"env_version,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// fetchCode 请求并返回二维码二进制数据
|
// fetchCode 请求并返回二维码二进制数据
|
||||||
|
|||||||
@@ -1,86 +0,0 @@
|
|||||||
package shortlink
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/silenceper/wechat/v2/miniprogram/context"
|
|
||||||
"github.com/silenceper/wechat/v2/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
generateShortLinkURL = "https://api.weixin.qq.com/wxa/genwxashortlink?access_token=%s"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ShortLink 短链接
|
|
||||||
type ShortLink struct {
|
|
||||||
*context.Context
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewShortLink 实例
|
|
||||||
func NewShortLink(ctx *context.Context) *ShortLink {
|
|
||||||
return &ShortLink{ctx}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShortLinker 请求结构体
|
|
||||||
type ShortLinker struct {
|
|
||||||
|
|
||||||
// pageUrl 通过 Short Link 进入的小程序页面路径,必须是已经发布的小程序存在的页面,可携带 query,最大1024个字符
|
|
||||||
PageURL string `json:"page_url"`
|
|
||||||
|
|
||||||
// pageTitle 页面标题,不能包含违法信息,超过20字符会用... 截断代替
|
|
||||||
PageTitle string `json:"page_title"`
|
|
||||||
|
|
||||||
// isPermanent 生成的 Short Link 类型,短期有效:false,永久有效:true
|
|
||||||
IsPermanent bool `json:"is_permanent,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// resShortLinker 返回结构体
|
|
||||||
type resShortLinker struct {
|
|
||||||
// 通用错误
|
|
||||||
*util.CommonError
|
|
||||||
|
|
||||||
// 返回的 shortLink
|
|
||||||
Link string `json:"link"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate 生成 shortLink
|
|
||||||
func (shortLink *ShortLink) generate(shortLinkParams ShortLinker) (string, error) {
|
|
||||||
var accessToken string
|
|
||||||
accessToken, err := shortLink.GetAccessToken()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
urlStr := fmt.Sprintf(generateShortLinkURL, accessToken)
|
|
||||||
response, err := util.PostJSON(urlStr, shortLinkParams)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用通用方法返回错误
|
|
||||||
var res resShortLinker
|
|
||||||
err = util.DecodeWithError(response, &res, "GenerateShortLink")
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.Link, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateShortLinkPermanent 生成永久shortLink
|
|
||||||
func (shortLink *ShortLink) GenerateShortLinkPermanent(PageURL, pageTitle string) (string, error) {
|
|
||||||
return shortLink.generate(ShortLinker{
|
|
||||||
PageURL: PageURL,
|
|
||||||
PageTitle: pageTitle,
|
|
||||||
IsPermanent: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateShortLinkTemp 生成临时shortLink
|
|
||||||
func (shortLink *ShortLink) GenerateShortLinkTemp(PageURL, pageTitle string) (string, error) {
|
|
||||||
return shortLink.generate(ShortLinker{
|
|
||||||
PageURL: PageURL,
|
|
||||||
PageTitle: pageTitle,
|
|
||||||
IsPermanent: false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
package urllink
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/silenceper/wechat/v2/miniprogram/context"
|
|
||||||
"github.com/silenceper/wechat/v2/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
// URLLink 小程序 URL Link
|
|
||||||
type URLLink struct {
|
|
||||||
*context.Context
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewURLLink 实例化
|
|
||||||
func NewURLLink(ctx *context.Context) *URLLink {
|
|
||||||
return &URLLink{Context: ctx}
|
|
||||||
}
|
|
||||||
|
|
||||||
const generateURL = "https://api.weixin.qq.com/wxa/generate_urllink"
|
|
||||||
|
|
||||||
// TExpireType 失效类型 (指定时间戳/指定间隔)
|
|
||||||
type TExpireType int
|
|
||||||
|
|
||||||
const (
|
|
||||||
// ExpireTypeTime 指定时间戳后失效
|
|
||||||
ExpireTypeTime TExpireType = 0
|
|
||||||
|
|
||||||
// ExpireTypeInterval 间隔指定天数后失效
|
|
||||||
ExpireTypeInterval TExpireType = 1
|
|
||||||
)
|
|
||||||
|
|
||||||
// ULParams 请求参数
|
|
||||||
// https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/url-link/urllink.generate.html#请求参数
|
|
||||||
type ULParams struct {
|
|
||||||
Path string `json:"path"`
|
|
||||||
Query string `json:"query"`
|
|
||||||
// envVersion 要打开的小程序版本。正式版为 "release",体验版为 "trial",开发版为 "develop"
|
|
||||||
EnvVersion string `json:"env_version,omitempty"`
|
|
||||||
IsExpire bool `json:"is_expire"`
|
|
||||||
ExpireType TExpireType `json:"expire_type"`
|
|
||||||
ExpireTime int64 `json:"expire_time"`
|
|
||||||
ExpireInterval int `json:"expire_interval"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ULResult 返回的结果
|
|
||||||
// https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/url-link/urllink.generate.html#返回值
|
|
||||||
type ULResult struct {
|
|
||||||
util.CommonError
|
|
||||||
|
|
||||||
URLLink string `json:"url_link"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate 生成url link
|
|
||||||
func (u *URLLink) Generate(params *ULParams) (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 ULResult
|
|
||||||
err = util.DecodeWithError(response, &resp, "URLLink.Generate")
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return resp.URLLink, nil
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"github.com/silenceper/wechat/v2/cache"
|
"github.com/silenceper/wechat/v2/cache"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config .config for 微信公众号
|
// Config config for 微信公众号
|
||||||
type Config struct {
|
type Config struct {
|
||||||
AppID string `json:"app_id"` //appid
|
AppID string `json:"app_id"` //appid
|
||||||
AppSecret string `json:"app_secret"` //appsecret
|
AppSecret string `json:"app_secret"` //appsecret
|
||||||
|
|||||||
@@ -1,228 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,248 +0,0 @@
|
|||||||
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,7 +4,6 @@ import (
|
|||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
|
|
||||||
"github.com/silenceper/wechat/v2/officialaccount/device"
|
"github.com/silenceper/wechat/v2/officialaccount/device"
|
||||||
"github.com/silenceper/wechat/v2/officialaccount/freepublish"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// MsgType 基本消息类型
|
// MsgType 基本消息类型
|
||||||
@@ -20,64 +19,60 @@ const (
|
|||||||
//MsgTypeText 表示文本消息
|
//MsgTypeText 表示文本消息
|
||||||
MsgTypeText MsgType = "text"
|
MsgTypeText MsgType = "text"
|
||||||
//MsgTypeImage 表示图片消息
|
//MsgTypeImage 表示图片消息
|
||||||
MsgTypeImage MsgType = "image"
|
MsgTypeImage = "image"
|
||||||
//MsgTypeVoice 表示语音消息
|
//MsgTypeVoice 表示语音消息
|
||||||
MsgTypeVoice MsgType = "voice"
|
MsgTypeVoice = "voice"
|
||||||
//MsgTypeVideo 表示视频消息
|
//MsgTypeVideo 表示视频消息
|
||||||
MsgTypeVideo MsgType = "video"
|
MsgTypeVideo = "video"
|
||||||
//MsgTypeMiniprogrampage 表示小程序卡片消息
|
//MsgTypeMiniprogrampage 表示小程序卡片消息
|
||||||
MsgTypeMiniprogrampage MsgType = "miniprogrampage"
|
MsgTypeMiniprogrampage = "miniprogrampage"
|
||||||
//MsgTypeShortVideo 表示短视频消息[限接收]
|
//MsgTypeShortVideo 表示短视频消息[限接收]
|
||||||
MsgTypeShortVideo MsgType = "shortvideo"
|
MsgTypeShortVideo = "shortvideo"
|
||||||
//MsgTypeLocation 表示坐标消息[限接收]
|
//MsgTypeLocation 表示坐标消息[限接收]
|
||||||
MsgTypeLocation MsgType = "location"
|
MsgTypeLocation = "location"
|
||||||
//MsgTypeLink 表示链接消息[限接收]
|
//MsgTypeLink 表示链接消息[限接收]
|
||||||
MsgTypeLink MsgType = "link"
|
MsgTypeLink = "link"
|
||||||
//MsgTypeMusic 表示音乐消息[限回复]
|
//MsgTypeMusic 表示音乐消息[限回复]
|
||||||
MsgTypeMusic MsgType = "music"
|
MsgTypeMusic = "music"
|
||||||
//MsgTypeNews 表示图文消息[限回复]
|
//MsgTypeNews 表示图文消息[限回复]
|
||||||
MsgTypeNews MsgType = "news"
|
MsgTypeNews = "news"
|
||||||
//MsgTypeTransfer 表示消息消息转发到客服
|
//MsgTypeTransfer 表示消息消息转发到客服
|
||||||
MsgTypeTransfer MsgType = "transfer_customer_service"
|
MsgTypeTransfer = "transfer_customer_service"
|
||||||
//MsgTypeEvent 表示事件推送消息
|
//MsgTypeEvent 表示事件推送消息
|
||||||
MsgTypeEvent MsgType = "event"
|
MsgTypeEvent = "event"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
//EventSubscribe 订阅
|
//EventSubscribe 订阅
|
||||||
EventSubscribe EventType = "subscribe"
|
EventSubscribe EventType = "subscribe"
|
||||||
//EventUnsubscribe 取消订阅
|
//EventUnsubscribe 取消订阅
|
||||||
EventUnsubscribe EventType = "unsubscribe"
|
EventUnsubscribe = "unsubscribe"
|
||||||
//EventScan 用户已经关注公众号,则微信会将带场景值扫描事件推送给开发者
|
//EventScan 用户已经关注公众号,则微信会将带场景值扫描事件推送给开发者
|
||||||
EventScan EventType = "SCAN"
|
EventScan = "SCAN"
|
||||||
//EventLocation 上报地理位置事件
|
//EventLocation 上报地理位置事件
|
||||||
EventLocation EventType = "LOCATION"
|
EventLocation = "LOCATION"
|
||||||
//EventClick 点击菜单拉取消息时的事件推送
|
//EventClick 点击菜单拉取消息时的事件推送
|
||||||
EventClick EventType = "CLICK"
|
EventClick = "CLICK"
|
||||||
//EventView 点击菜单跳转链接时的事件推送
|
//EventView 点击菜单跳转链接时的事件推送
|
||||||
EventView EventType = "VIEW"
|
EventView = "VIEW"
|
||||||
//EventScancodePush 扫码推事件的事件推送
|
//EventScancodePush 扫码推事件的事件推送
|
||||||
EventScancodePush EventType = "scancode_push"
|
EventScancodePush = "scancode_push"
|
||||||
//EventScancodeWaitmsg 扫码推事件且弹出“消息接收中”提示框的事件推送
|
//EventScancodeWaitmsg 扫码推事件且弹出“消息接收中”提示框的事件推送
|
||||||
EventScancodeWaitmsg EventType = "scancode_waitmsg"
|
EventScancodeWaitmsg = "scancode_waitmsg"
|
||||||
//EventPicSysphoto 弹出系统拍照发图的事件推送
|
//EventPicSysphoto 弹出系统拍照发图的事件推送
|
||||||
EventPicSysphoto EventType = "pic_sysphoto"
|
EventPicSysphoto = "pic_sysphoto"
|
||||||
//EventPicPhotoOrAlbum 弹出拍照或者相册发图的事件推送
|
//EventPicPhotoOrAlbum 弹出拍照或者相册发图的事件推送
|
||||||
EventPicPhotoOrAlbum EventType = "pic_photo_or_album"
|
EventPicPhotoOrAlbum = "pic_photo_or_album"
|
||||||
//EventPicWeixin 弹出微信相册发图器的事件推送
|
//EventPicWeixin 弹出微信相册发图器的事件推送
|
||||||
EventPicWeixin EventType = "pic_weixin"
|
EventPicWeixin = "pic_weixin"
|
||||||
//EventLocationSelect 弹出地理位置选择器的事件推送
|
//EventLocationSelect 弹出地理位置选择器的事件推送
|
||||||
EventLocationSelect EventType = "location_select"
|
EventLocationSelect = "location_select"
|
||||||
//EventTemplateSendJobFinish 发送模板消息推送通知
|
//EventTemplateSendJobFinish 发送模板消息推送通知
|
||||||
EventTemplateSendJobFinish EventType = "TEMPLATESENDJOBFINISH"
|
EventTemplateSendJobFinish = "TEMPLATESENDJOBFINISH"
|
||||||
//EventMassSendJobFinish 群发消息推送通知
|
//EventMassSendJobFinish 群发消息推送通知
|
||||||
EventMassSendJobFinish EventType = "MASSSENDJOBFINISH"
|
EventMassSendJobFinish = "MASSSENDJOBFINISH"
|
||||||
//EventWxaMediaCheck 异步校验图片/音频是否含有违法违规内容推送事件
|
//EventWxaMediaCheck 异步校验图片/音频是否含有违法违规内容推送事件
|
||||||
EventWxaMediaCheck EventType = "wxa_media_check"
|
EventWxaMediaCheck = "wxa_media_check"
|
||||||
// EventSubscribeMsgPopupEvent 订阅通知事件推送
|
|
||||||
EventSubscribeMsgPopupEvent EventType = "subscribe_msg_popup_event"
|
|
||||||
// EventPublishJobFinish 发布任务完成
|
|
||||||
EventPublishJobFinish EventType = "PUBLISHJOBFINISH"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -86,13 +81,11 @@ const (
|
|||||||
// InfoTypeVerifyTicket 返回ticket
|
// InfoTypeVerifyTicket 返回ticket
|
||||||
InfoTypeVerifyTicket InfoType = "component_verify_ticket"
|
InfoTypeVerifyTicket InfoType = "component_verify_ticket"
|
||||||
// InfoTypeAuthorized 授权
|
// InfoTypeAuthorized 授权
|
||||||
InfoTypeAuthorized InfoType = "authorized"
|
InfoTypeAuthorized = "authorized"
|
||||||
// InfoTypeUnauthorized 取消授权
|
// InfoTypeUnauthorized 取消授权
|
||||||
InfoTypeUnauthorized InfoType = "unauthorized"
|
InfoTypeUnauthorized = "unauthorized"
|
||||||
// InfoTypeUpdateAuthorized 更新授权
|
// InfoTypeUpdateAuthorized 更新授权
|
||||||
InfoTypeUpdateAuthorized InfoType = "updateauthorized"
|
InfoTypeUpdateAuthorized = "updateauthorized"
|
||||||
// InfoTypeNotifyThirdFasterRegister 注册审核事件推送
|
|
||||||
InfoTypeNotifyThirdFasterRegister InfoType = "notify_third_fasteregister"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
//MixMessage 存放所有微信发送过来的消息和事件
|
//MixMessage 存放所有微信发送过来的消息和事件
|
||||||
@@ -149,25 +142,6 @@ type MixMessage struct {
|
|||||||
Poiname string `xml:"Poiname"`
|
Poiname string `xml:"Poiname"`
|
||||||
}
|
}
|
||||||
|
|
||||||
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"`
|
InfoType InfoType `xml:"InfoType"`
|
||||||
AppID string `xml:"AppId"`
|
AppID string `xml:"AppId"`
|
||||||
@@ -176,15 +150,6 @@ type MixMessage struct {
|
|||||||
AuthorizationCode string `xml:"AuthorizationCode"`
|
AuthorizationCode string `xml:"AuthorizationCode"`
|
||||||
AuthorizationCodeExpiredTime int64 `xml:"AuthorizationCodeExpiredTime"`
|
AuthorizationCodeExpiredTime int64 `xml:"AuthorizationCodeExpiredTime"`
|
||||||
PreAuthCode string `xml:"PreAuthCode"`
|
PreAuthCode string `xml:"PreAuthCode"`
|
||||||
AuthCode string `xml:"auth_code"`
|
|
||||||
Info struct {
|
|
||||||
Name string `xml:"name"`
|
|
||||||
Code string `xml:"code"`
|
|
||||||
CodeType int `xml:"code_type"`
|
|
||||||
LegalPersonaWechat string `xml:"legal_persona_wechat"`
|
|
||||||
LegalPersonaName string `xml:"legal_persona_name"`
|
|
||||||
ComponentPhone string `xml:"component_phone"`
|
|
||||||
} `xml:"info"`
|
|
||||||
|
|
||||||
// 卡券相关
|
// 卡券相关
|
||||||
CardID string `xml:"CardId"`
|
CardID string `xml:"CardId"`
|
||||||
@@ -207,13 +172,6 @@ type MixMessage struct {
|
|||||||
device.MsgDevice
|
device.MsgDevice
|
||||||
}
|
}
|
||||||
|
|
||||||
// SubscribeMsgPopupEvent 订阅通知事件推送的消息体
|
|
||||||
type SubscribeMsgPopupEvent struct {
|
|
||||||
TemplateID string `xml:"TemplateId"`
|
|
||||||
SubscribeStatusString string `xml:"SubscribeStatusString"`
|
|
||||||
PopupScene int `xml:"PopupScene"`
|
|
||||||
}
|
|
||||||
|
|
||||||
//EventPic 发图事件推送
|
//EventPic 发图事件推送
|
||||||
type EventPic struct {
|
type EventPic struct {
|
||||||
PicMd5Sum string `xml:"PicMd5Sum"`
|
PicMd5Sum string `xml:"PicMd5Sum"`
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ package officialaccount
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"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/ocr"
|
||||||
|
|
||||||
"github.com/silenceper/wechat/v2/officialaccount/datacube"
|
"github.com/silenceper/wechat/v2/officialaccount/datacube"
|
||||||
@@ -82,16 +80,6 @@ func (officialAccount *OfficialAccount) GetMaterial() *material.Material {
|
|||||||
return material.NewMaterial(officialAccount.ctx)
|
return material.NewMaterial(officialAccount.ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDraft 草稿箱
|
|
||||||
func (officialAccount *OfficialAccount) GetDraft() *draft.Draft {
|
|
||||||
return draft.NewDraft(officialAccount.ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFreePublish 发布能力
|
|
||||||
func (officialAccount *OfficialAccount) GetFreePublish() *freepublish.FreePublish {
|
|
||||||
return freepublish.NewFreePublish(officialAccount.ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetJs js-sdk配置
|
// GetJs js-sdk配置
|
||||||
func (officialAccount *OfficialAccount) GetJs() *js.Js {
|
func (officialAccount *OfficialAccount) GetJs() *js.Js {
|
||||||
return js.NewJs(officialAccount.ctx)
|
return js.NewJs(officialAccount.ctx)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"github.com/silenceper/wechat/v2/cache"
|
"github.com/silenceper/wechat/v2/cache"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config .config for 微信开放平台
|
//Config config for 微信开放平台
|
||||||
type Config struct {
|
type Config struct {
|
||||||
AppID string `json:"app_id"` //appid
|
AppID string `json:"app_id"` //appid
|
||||||
AppSecret string `json:"app_secret"` //appsecret
|
AppSecret string `json:"app_secret"` //appsecret
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ const (
|
|||||||
|
|
||||||
// ComponentAccessToken 第三方平台
|
// ComponentAccessToken 第三方平台
|
||||||
type ComponentAccessToken struct {
|
type ComponentAccessToken struct {
|
||||||
util.CommonError
|
|
||||||
AccessToken string `json:"component_access_token"`
|
AccessToken string `json:"component_access_token"`
|
||||||
ExpiresIn int64 `json:"expires_in"`
|
ExpiresIn int64 `json:"expires_in"`
|
||||||
}
|
}
|
||||||
@@ -58,10 +57,6 @@ func (ctx *Context) SetComponentAccessToken(verifyTicket string) (*ComponentAcce
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if at.ErrCode != 0 {
|
|
||||||
return nil, fmt.Errorf("SetComponentAccessToken Error , errcode=%d , errmsg=%s", at.ErrCode, at.ErrMsg)
|
|
||||||
}
|
|
||||||
|
|
||||||
accessTokenCacheKey := fmt.Sprintf("component_access_token_%s", ctx.AppID)
|
accessTokenCacheKey := fmt.Sprintf("component_access_token_%s", ctx.AppID)
|
||||||
expires := at.ExpiresIn - 1500
|
expires := at.ExpiresIn - 1500
|
||||||
if err := ctx.Cache.Set(accessTokenCacheKey, at.AccessToken, time.Duration(expires)*time.Second); err != nil {
|
if err := ctx.Cache.Set(accessTokenCacheKey, at.AccessToken, time.Duration(expires)*time.Second); err != nil {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
// Config .config for pay
|
// Config config for pay
|
||||||
type Config struct {
|
type Config struct {
|
||||||
AppID string `json:"app_id"`
|
AppID string `json:"app_id"`
|
||||||
MchID string `json:"mch_id"`
|
MchID string `json:"mch_id"`
|
||||||
|
|||||||
@@ -1,98 +0,0 @@
|
|||||||
package order
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/xml"
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/silenceper/wechat/v2/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
// https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_3
|
|
||||||
var closeGateway = "https://api.mch.weixin.qq.com/pay/closeorder"
|
|
||||||
|
|
||||||
// CloseParams 传入的参数
|
|
||||||
type CloseParams struct {
|
|
||||||
OutTradeNo string // 商户订单号
|
|
||||||
SignType string // 签名类型
|
|
||||||
}
|
|
||||||
|
|
||||||
// closeRequest 接口请求参数
|
|
||||||
type closeRequest struct {
|
|
||||||
AppID string `xml:"appid"` // 公众账号ID
|
|
||||||
MchID string `xml:"mch_id"` // 商户号
|
|
||||||
NonceStr string `xml:"nonce_str"` // 随机字符串
|
|
||||||
Sign string `xml:"sign"` // 签名
|
|
||||||
SignType string `xml:"sign_type,omitempty"` // 签名类型
|
|
||||||
OutTradeNo string `xml:"out_trade_no"` // 商户订单号
|
|
||||||
}
|
|
||||||
|
|
||||||
// CloseResult 关闭订单返回结果
|
|
||||||
type CloseResult struct {
|
|
||||||
ReturnCode *string `xml:"return_code"`
|
|
||||||
ReturnMsg *string `xml:"return_msg"`
|
|
||||||
|
|
||||||
AppID *string `xml:"appid" json:"appid"`
|
|
||||||
MchID *string `xml:"mch_id"`
|
|
||||||
NonceStr *string `xml:"nonce_str"`
|
|
||||||
Sign *string `xml:"sign"`
|
|
||||||
ResultCode *string `xml:"result_code"`
|
|
||||||
ResultMsg *string `xml:"result_msg"`
|
|
||||||
ErrCode *string `xml:"err_code"`
|
|
||||||
ErrCodeDes *string `xml:"err_code_des"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// CloseOrder 关闭订单
|
|
||||||
func (o *Order) CloseOrder(p *CloseParams) (closeResult CloseResult, err error) {
|
|
||||||
nonceStr := util.RandomStr(32)
|
|
||||||
// 签名类型
|
|
||||||
if p.SignType == "" {
|
|
||||||
p.SignType = "MD5"
|
|
||||||
}
|
|
||||||
|
|
||||||
params := make(map[string]string)
|
|
||||||
params["appid"] = o.AppID
|
|
||||||
params["mch_id"] = o.MchID
|
|
||||||
params["nonce_str"] = nonceStr
|
|
||||||
params["out_trade_no"] = p.OutTradeNo
|
|
||||||
params["sign_type"] = p.SignType
|
|
||||||
|
|
||||||
var (
|
|
||||||
sign string
|
|
||||||
rawRet []byte
|
|
||||||
)
|
|
||||||
|
|
||||||
sign, err = util.ParamSign(params, o.Key)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
request := closeRequest{
|
|
||||||
AppID: o.AppID,
|
|
||||||
MchID: o.MchID,
|
|
||||||
NonceStr: nonceStr,
|
|
||||||
Sign: sign,
|
|
||||||
OutTradeNo: p.OutTradeNo,
|
|
||||||
SignType: p.SignType,
|
|
||||||
}
|
|
||||||
|
|
||||||
rawRet, err = util.PostXML(closeGateway, request)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = xml.Unmarshal(rawRet, &closeResult)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if *closeResult.ReturnCode == SUCCESS {
|
|
||||||
// close success
|
|
||||||
if *closeResult.ResultCode == SUCCESS {
|
|
||||||
err = nil
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = errors.New(*closeResult.ErrCode + *closeResult.ErrCodeDes)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = errors.New("[msg : xmlUnmarshalError] [rawReturn : " + string(rawRet) + "] [sign : " + sign + "]")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
@@ -172,9 +172,9 @@ func (o *Order) BridgeConfig(p *Params) (cfg Config, err error) {
|
|||||||
// BridgeAppConfig get app bridge config
|
// BridgeAppConfig get app bridge config
|
||||||
func (o *Order) BridgeAppConfig(p *Params) (cfg ConfigForApp, err error) {
|
func (o *Order) BridgeAppConfig(p *Params) (cfg ConfigForApp, err error) {
|
||||||
var (
|
var (
|
||||||
timestamp = strconv.FormatInt(time.Now().Unix(), 10)
|
timestamp string = strconv.FormatInt(time.Now().Unix(), 10)
|
||||||
noncestr = util.RandomStr(32)
|
noncestr string = util.RandomStr(32)
|
||||||
_package = "Sign=WXPay"
|
_package string = "Sign=WXPay"
|
||||||
)
|
)
|
||||||
order, err := o.PrePayOrder(p)
|
order, err := o.PrePayOrder(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -210,17 +210,6 @@ func (o *Order) BridgeAppConfig(p *Params) (cfg ConfigForApp, err error) {
|
|||||||
// PrePayOrder return data for invoke wechat payment
|
// PrePayOrder return data for invoke wechat payment
|
||||||
func (o *Order) PrePayOrder(p *Params) (payOrder PreOrder, err error) {
|
func (o *Order) PrePayOrder(p *Params) (payOrder PreOrder, err error) {
|
||||||
nonceStr := util.RandomStr(32)
|
nonceStr := util.RandomStr(32)
|
||||||
|
|
||||||
// 通知地址
|
|
||||||
if len(p.NotifyURL) == 0 {
|
|
||||||
p.NotifyURL = o.NotifyURL // 默认使用order.NotifyURL
|
|
||||||
}
|
|
||||||
|
|
||||||
// 签名类型
|
|
||||||
if p.SignType == "" {
|
|
||||||
p.SignType = util.SignTypeMD5
|
|
||||||
}
|
|
||||||
|
|
||||||
param := map[string]string{
|
param := map[string]string{
|
||||||
"appid": o.AppID,
|
"appid": o.AppID,
|
||||||
"body": p.Body,
|
"body": p.Body,
|
||||||
@@ -235,7 +224,15 @@ func (o *Order) PrePayOrder(p *Params) (payOrder PreOrder, err error) {
|
|||||||
"detail": p.Detail,
|
"detail": p.Detail,
|
||||||
"attach": p.Attach,
|
"attach": p.Attach,
|
||||||
"goods_tag": p.GoodsTag,
|
"goods_tag": p.GoodsTag,
|
||||||
"notify_url": p.NotifyURL,
|
}
|
||||||
|
// 签名类型
|
||||||
|
if param["sign_type"] == "" {
|
||||||
|
param["sign_type"] = util.SignTypeMD5
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通知地址
|
||||||
|
if p.NotifyURL != "" {
|
||||||
|
param["notify_url"] = p.NotifyURL
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.TimeExpire != "" {
|
if p.TimeExpire != "" {
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ type Response struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//WalletTransfer 付款到零钱
|
//WalletTransfer 付款到零钱
|
||||||
func (transfer *Transfer) WalletTransfer(p *Params) (rsp *Response, err error) {
|
func (transfer *Transfer) WalletTransfer(p *Params) (rsp Response, err error) {
|
||||||
nonceStr := util.RandomStr(32)
|
nonceStr := util.RandomStr(32)
|
||||||
param := make(map[string]string)
|
param := make(map[string]string)
|
||||||
param["mch_appid"] = transfer.AppID
|
param["mch_appid"] = transfer.AppID
|
||||||
@@ -83,10 +83,11 @@ func (transfer *Transfer) WalletTransfer(p *Params) (rsp *Response, err error) {
|
|||||||
if p.DeviceInfo != "" {
|
if p.DeviceInfo != "" {
|
||||||
param["device_info"] = p.DeviceInfo
|
param["device_info"] = p.DeviceInfo
|
||||||
}
|
}
|
||||||
param["check_name"] = "NO_CHECK"
|
|
||||||
if p.CheckName {
|
if p.CheckName {
|
||||||
param["check_name"] = "FORCE_CHECK"
|
param["check_name"] = "FORCE_CHECK"
|
||||||
param["re_user_name"] = p.ReUserName
|
param["re_user_name"] = p.ReUserName
|
||||||
|
} else {
|
||||||
|
param["check_name"] = "NO_CHECK"
|
||||||
}
|
}
|
||||||
if p.SpbillCreateIP != "" {
|
if p.SpbillCreateIP != "" {
|
||||||
param["spbill_create_ip"] = p.SpbillCreateIP
|
param["spbill_create_ip"] = p.SpbillCreateIP
|
||||||
@@ -109,11 +110,13 @@ func (transfer *Transfer) WalletTransfer(p *Params) (rsp *Response, err error) {
|
|||||||
Desc: p.Desc,
|
Desc: p.Desc,
|
||||||
SpbillCreateIP: p.SpbillCreateIP,
|
SpbillCreateIP: p.SpbillCreateIP,
|
||||||
}
|
}
|
||||||
req.CheckName = "NO_CHECK"
|
|
||||||
if p.CheckName {
|
if p.CheckName {
|
||||||
req.CheckName = "FORCE_CHECK"
|
req.CheckName = "FORCE_CHECK"
|
||||||
req.ReUserName = p.ReUserName
|
req.ReUserName = p.ReUserName
|
||||||
|
} else {
|
||||||
|
req.CheckName = "NO_CHECK"
|
||||||
}
|
}
|
||||||
|
|
||||||
rawRet, err := util.PostXMLWithTLS(walletTransferGateway, req, p.RootCa, transfer.MchID)
|
rawRet, err := util.PostXMLWithTLS(walletTransferGateway, req, p.RootCa, transfer.MchID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
|||||||
23
util/http.go
23
util/http.go
@@ -2,7 +2,6 @@ package util
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
@@ -20,16 +19,7 @@ import (
|
|||||||
|
|
||||||
//HTTPGet get 请求
|
//HTTPGet get 请求
|
||||||
func HTTPGet(uri string) ([]byte, error) {
|
func HTTPGet(uri string) ([]byte, error) {
|
||||||
return HTTPGetContext(context.Background(), uri)
|
response, err := http.Get(uri)
|
||||||
}
|
|
||||||
|
|
||||||
// HTTPGetContext get 请求
|
|
||||||
func HTTPGetContext(ctx context.Context, uri string) ([]byte, error) {
|
|
||||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
response, err := http.DefaultClient.Do(request)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -43,17 +33,8 @@ func HTTPGetContext(ctx context.Context, uri string) ([]byte, error) {
|
|||||||
|
|
||||||
//HTTPPost post 请求
|
//HTTPPost post 请求
|
||||||
func HTTPPost(uri string, data string) ([]byte, error) {
|
func HTTPPost(uri string, data string) ([]byte, error) {
|
||||||
return HTTPPostContext(context.Background(), uri, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTPPostContext post 请求
|
|
||||||
func HTTPPostContext(ctx context.Context, uri string, data string) ([]byte, error) {
|
|
||||||
body := bytes.NewBuffer([]byte(data))
|
body := bytes.NewBuffer([]byte(data))
|
||||||
request, err := http.NewRequestWithContext(ctx, http.MethodPost, uri, body)
|
response, err := http.Post(uri, "", body)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
response, err := http.DefaultClient.Do(request)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,8 +17,6 @@ const (
|
|||||||
SDKUnknownError Error = "未知错误"
|
SDKUnknownError Error = "未知错误"
|
||||||
// SDKInvalidCredential 错误码:40001
|
// SDKInvalidCredential 错误码:40001
|
||||||
SDKInvalidCredential Error = "不合法的secret参数"
|
SDKInvalidCredential Error = "不合法的secret参数"
|
||||||
// SDKInvalidImageSize 错误码:40009
|
|
||||||
SDKInvalidImageSize Error = "无效的图片大小"
|
|
||||||
// SDKInvalidCorpID 错误码:40013
|
// SDKInvalidCorpID 错误码:40013
|
||||||
SDKInvalidCorpID Error = "无效的 CorpID"
|
SDKInvalidCorpID Error = "无效的 CorpID"
|
||||||
// SDKAccessTokenInvalid 错误码:40014
|
// SDKAccessTokenInvalid 错误码:40014
|
||||||
@@ -27,10 +25,6 @@ const (
|
|||||||
SDKValidateSignatureFailed Error = "校验签名错误"
|
SDKValidateSignatureFailed Error = "校验签名错误"
|
||||||
// SDKDecryptMSGFailed 错误码:40016
|
// SDKDecryptMSGFailed 错误码:40016
|
||||||
SDKDecryptMSGFailed Error = "消息解密失败"
|
SDKDecryptMSGFailed Error = "消息解密失败"
|
||||||
// SDKMediaIDExceedMinLength 错误码:40058
|
|
||||||
SDKMediaIDExceedMinLength Error = "不合法的参数, 请参照具体 API 接口说明进行传参"
|
|
||||||
// SDKContentContainsSensitiveInformation 错误码:40201
|
|
||||||
SDKContentContainsSensitiveInformation Error = "当前客服账号由于涉及敏感信息,已被封禁,请联系企业微信客服处理"
|
|
||||||
// SDKAccessTokenMissing 错误码:41001
|
// SDKAccessTokenMissing 错误码:41001
|
||||||
SDKAccessTokenMissing Error = "缺少AccessToken参数"
|
SDKAccessTokenMissing Error = "缺少AccessToken参数"
|
||||||
// SDKAccessTokenExpired 错误码:42001
|
// SDKAccessTokenExpired 错误码:42001
|
||||||
@@ -45,8 +39,6 @@ const (
|
|||||||
SDKOpenKFIDNotExist Error = "open_kfid 不存在"
|
SDKOpenKFIDNotExist Error = "open_kfid 不存在"
|
||||||
// SDKWeWorkAlready 错误码:95011
|
// SDKWeWorkAlready 错误码:95011
|
||||||
SDKWeWorkAlready Error = "已在企业微信使用微信客服"
|
SDKWeWorkAlready Error = "已在企业微信使用微信客服"
|
||||||
// SDKNotUseInWeCom 错误码:95012
|
|
||||||
SDKNotUseInWeCom Error = "未在企业微信使用微信客服"
|
|
||||||
// SDKApiNotOpen 错误码:95017
|
// SDKApiNotOpen 错误码:95017
|
||||||
SDKApiNotOpen Error = "API 功能没有被开启"
|
SDKApiNotOpen Error = "API 功能没有被开启"
|
||||||
)
|
)
|
||||||
@@ -56,38 +48,44 @@ func (r Error) Error() string {
|
|||||||
return reflect.ValueOf(r).String()
|
return reflect.ValueOf(r).String()
|
||||||
}
|
}
|
||||||
|
|
||||||
var codeDic = map[int64]error{
|
|
||||||
50001: SDKInitFailed,
|
|
||||||
50002: SDKCacheUnavailable,
|
|
||||||
50003: SDKUnknownError,
|
|
||||||
40001: SDKInvalidCredential,
|
|
||||||
40009: SDKInvalidImageSize,
|
|
||||||
40013: SDKInvalidCorpID,
|
|
||||||
40014: SDKAccessTokenInvalid,
|
|
||||||
40015: SDKValidateSignatureFailed,
|
|
||||||
40016: SDKDecryptMSGFailed,
|
|
||||||
40058: SDKMediaIDExceedMinLength,
|
|
||||||
40201: SDKContentContainsSensitiveInformation,
|
|
||||||
41001: SDKAccessTokenMissing,
|
|
||||||
42001: SDKAccessTokenExpired,
|
|
||||||
45009: SDKApiFreqOutOfLimit,
|
|
||||||
48002: SDKApiForbidden,
|
|
||||||
95000: SDKInvalidOpenKFID,
|
|
||||||
95004: SDKOpenKFIDNotExist,
|
|
||||||
95011: SDKWeWorkAlready,
|
|
||||||
95012: SDKNotUseInWeCom,
|
|
||||||
95017: SDKApiNotOpen,
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSDKErr 初始化SDK实例错误信息
|
// NewSDKErr 初始化SDK实例错误信息
|
||||||
func NewSDKErr(code int64, msgList ...string) error {
|
func NewSDKErr(code int64, msgList ...string) Error {
|
||||||
if err := codeDic[code]; err != nil {
|
switch code {
|
||||||
return err
|
case 50001:
|
||||||
}
|
return SDKInitFailed
|
||||||
|
case 50002:
|
||||||
|
return SDKCacheUnavailable
|
||||||
|
case 40001:
|
||||||
|
return SDKInvalidCredential
|
||||||
|
case 41001:
|
||||||
|
return SDKAccessTokenMissing
|
||||||
|
case 42001:
|
||||||
|
return SDKAccessTokenExpired
|
||||||
|
case 40013:
|
||||||
|
return SDKInvalidCorpID
|
||||||
|
case 40014:
|
||||||
|
return SDKAccessTokenInvalid
|
||||||
|
case 40015:
|
||||||
|
return SDKValidateSignatureFailed
|
||||||
|
case 40016:
|
||||||
|
return SDKDecryptMSGFailed
|
||||||
|
case 45009:
|
||||||
|
return SDKApiFreqOutOfLimit
|
||||||
|
case 48002:
|
||||||
|
return SDKApiForbidden
|
||||||
|
case 95000:
|
||||||
|
return SDKInvalidOpenKFID
|
||||||
|
case 95004:
|
||||||
|
return SDKOpenKFIDNotExist
|
||||||
|
case 95011:
|
||||||
|
return SDKWeWorkAlready
|
||||||
|
case 95017:
|
||||||
|
return SDKApiNotOpen
|
||||||
|
default:
|
||||||
//返回未知的自定义错误
|
//返回未知的自定义错误
|
||||||
if len(msgList) > 0 {
|
if len(msgList) > 0 {
|
||||||
return Error(strings.Join(msgList, ","))
|
return Error(strings.Join(msgList, ","))
|
||||||
}
|
}
|
||||||
return SDKUnknownError
|
return SDKUnknownError
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
package kf
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/silenceper/wechat/v2/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
//获取视频号绑定状态
|
|
||||||
corpQualification = "https://qyapi.weixin.qq.com/cgi-bin/kf/get_corp_qualification?access_token=%s"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CorpQualificationSchema 获取视频号绑定状态响应内容
|
|
||||||
type CorpQualificationSchema struct {
|
|
||||||
util.CommonError
|
|
||||||
WechatChannelsBinding bool `json:"wechat_channels_binding"` // 当企业具有绑定成功的视频号时,返回true,否则返回false。 1. 企业申请绑定视频号且由视频号管理员确认后,才为绑定成功状态 2. 至少有一个绑定成功的视频号就会返回true
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCorpQualification 获取视频号绑定状态
|
|
||||||
// 微信客服可接待的客户数,和企业是否已完成主体验证、是否绑定视频号相关。
|
|
||||||
//
|
|
||||||
// 企业未完成主体验证时,微信客服仅可累计接待100位客户
|
|
||||||
// 企业已验证但未绑定视频号时,微信客服仅可累计接待10000位客户
|
|
||||||
// 企业已验证且已绑定视频号时,微信客服可接待的客户数不受限制
|
|
||||||
//
|
|
||||||
// 开发者可获取状态后,在应用等地方提示企业去完成主体验证或绑定视频号。
|
|
||||||
func (r *Client) GetCorpQualification() (info CorpQualificationSchema, err error) {
|
|
||||||
var (
|
|
||||||
accessToken string
|
|
||||||
data []byte
|
|
||||||
)
|
|
||||||
accessToken, err = r.ctx.GetAccessToken()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
data, err = util.HTTPGet(fmt.Sprintf(corpQualification, accessToken))
|
|
||||||
if err != nil {
|
|
||||||
return info, err
|
|
||||||
}
|
|
||||||
if err = json.Unmarshal(data, &info); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if info.ErrCode != 0 {
|
|
||||||
return info, NewSDKErr(info.ErrCode, info.ErrMsg)
|
|
||||||
}
|
|
||||||
return info, nil
|
|
||||||
}
|
|
||||||
@@ -18,14 +18,7 @@ type SendMsgSchema struct {
|
|||||||
MsgID string `json:"msgid"` // 消息ID。如果请求参数指定了msgid,则原样返回,否则系统自动生成并返回。不多于32字节, 字符串取值范围(正则表达式):[0-9a-zA-Z_-]*
|
MsgID string `json:"msgid"` // 消息ID。如果请求参数指定了msgid,则原样返回,否则系统自动生成并返回。不多于32字节, 字符串取值范围(正则表达式):[0-9a-zA-Z_-]*
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendMsg 发送消息
|
// SendMsg 获取消息
|
||||||
// 当微信客户处于“新接入待处理”或“由智能助手接待”状态下,可调用该接口给用户发送消息。
|
|
||||||
// 注意仅当微信客户在主动发送消息给客服后的48小时内,企业可发送消息给客户,最多可发送5条消息;若用户继续发送消息,企业可再次下发消息。
|
|
||||||
// 支持发送消息类型:文本、图片、语音、视频、文件、图文、小程序、菜单消息、地理位置。
|
|
||||||
// 目前该接口允许下发消息条数和下发时限如下:
|
|
||||||
//
|
|
||||||
// 用户动作 允许下发条数限制 下发时限
|
|
||||||
// 用户发送消息 5条 48 小时
|
|
||||||
func (r *Client) SendMsg(options interface{}) (info SendMsgSchema, err error) {
|
func (r *Client) SendMsg(options interface{}) (info SendMsgSchema, err error) {
|
||||||
var (
|
var (
|
||||||
accessToken string
|
accessToken string
|
||||||
|
|||||||
@@ -82,8 +82,7 @@ type Menu struct {
|
|||||||
MsgType string `json:"msgtype"` // 消息类型,此时固定为:msgmenu
|
MsgType string `json:"msgtype"` // 消息类型,此时固定为:msgmenu
|
||||||
MsgMenu struct {
|
MsgMenu struct {
|
||||||
HeadContent string `json:"head_content"` // 消息内容,不多于1024字节
|
HeadContent string `json:"head_content"` // 消息内容,不多于1024字节
|
||||||
List []interface{} `json:"list"` // 菜单项配置,不能多余10个
|
List []interface{} `json:"list"` // 菜单项配置
|
||||||
TailContent string `json:"tail_content"` // 结束文本, 不多于1024字
|
|
||||||
} `json:"msgmenu"`
|
} `json:"msgmenu"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
package kf
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/silenceper/wechat/v2/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// 发送事件响应消息
|
|
||||||
sendMsgOnEventAddr = "https://qyapi.weixin.qq.com/cgi-bin/kf/send_msg_on_event?access_token=%s"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SendMsgOnEventSchema 发送事件响应消息
|
|
||||||
type SendMsgOnEventSchema struct {
|
|
||||||
util.CommonError
|
|
||||||
MsgID string `json:"msgid"` // 消息ID。如果请求参数指定了msgid,则原样返回,否则系统自动生成并返回。不多于32字节, 字符串取值范围(正则表达式):[0-9a-zA-Z_-]*
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendMsgOnEvent 发送事件响应消息
|
|
||||||
// 当特定的事件回调消息包含code字段,或通过接口变更到特定的会话状态,会返回code字段。
|
|
||||||
// 开发者可以此code为凭证,调用该接口给用户发送相应事件场景下的消息,如客服欢迎语、客服提示语和会话结束语等。
|
|
||||||
// 除”用户进入会话事件”以外,响应消息仅支持会话处于获取该code的会话状态时发送,如将会话转入待接入池时获得的code仅能在会话状态为”待接入池排队中“时发送。
|
|
||||||
//
|
|
||||||
// 目前支持的事件场景和相关约束如下:
|
|
||||||
//
|
|
||||||
// 事件场景 允许下发条数 code有效期 支持的消息类型 获取code途径
|
|
||||||
// 用户进入会话,用于发送客服欢迎语 1条 20秒 文本、菜单 事件回调
|
|
||||||
// 进入接待池,用于发送排队提示语等 1条 48小时 文本 转接会话接口
|
|
||||||
// 从接待池接入会话,用于发送非工作时间的提示语或超时未回复的提示语等 1条 48小时 文本 事件回调、转接会话接口
|
|
||||||
// 结束会话,用于发送结束会话提示语或满意度评价等 1条 20秒 文本、菜单 事件回调、转接会话接口
|
|
||||||
//
|
|
||||||
//「进入会话事件」响应消息:
|
|
||||||
// 如果满足通过API下发欢迎语条件(条件为:1. 企业没有在管理端配置了原生欢迎语;2. 用户在过去48小时里未收过欢迎语,且未向该用户发过消息),则用户进入会话事件会额外返回一个welcome_code,开发者以此为凭据调用接口(填到该接口code参数),即可向客户发送客服欢迎语。
|
|
||||||
func (r *Client) SendMsgOnEvent(options interface{}) (info SendMsgOnEventSchema, err error) {
|
|
||||||
var (
|
|
||||||
accessToken string
|
|
||||||
data []byte
|
|
||||||
)
|
|
||||||
accessToken, err = r.ctx.GetAccessToken()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
data, err = util.PostJSON(fmt.Sprintf(sendMsgOnEventAddr, accessToken), options)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err = json.Unmarshal(data, &info); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if info.ErrCode != 0 {
|
|
||||||
return info, NewSDKErr(info.ErrCode, info.ErrMsg)
|
|
||||||
}
|
|
||||||
return info, nil
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
package sendmsgonevent
|
|
||||||
|
|
||||||
// Message 发送事件响应消息
|
|
||||||
type Message struct {
|
|
||||||
Code string `json:"code"` // 事件响应消息对应的code。通过事件回调下发,仅可使用一次。
|
|
||||||
MsgID string `json:"msgid"` // 消息ID。如果请求参数指定了msgid,则原样返回,否则系统自动生成并返回。不多于32字节,不多于32字节
|
|
||||||
}
|
|
||||||
|
|
||||||
// Text 文本消息
|
|
||||||
type Text struct {
|
|
||||||
Message
|
|
||||||
MsgType string `json:"msgtype"` // 消息类型,此时固定为:text
|
|
||||||
Text struct {
|
|
||||||
Content string `json:"content"` // 消息内容,最长不超过2048个字节
|
|
||||||
} `json:"text"` // 文本消息
|
|
||||||
}
|
|
||||||
|
|
||||||
// Menu 发送菜单消息
|
|
||||||
type Menu struct {
|
|
||||||
Message
|
|
||||||
MsgType string `json:"msgtype"` // 消息类型,此时固定为:msgmenu
|
|
||||||
MsgMenu struct {
|
|
||||||
HeadContent string `json:"head_content"` // 消息内容,不多于1024字节
|
|
||||||
List []interface{} `json:"list"` // 菜单项配置,不能多余10个
|
|
||||||
TailContent string `json:"tail_content"` // 结束文本, 不多于1024字
|
|
||||||
} `json:"msgmenu"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// MenuClick 回复菜单
|
|
||||||
type MenuClick struct {
|
|
||||||
Type string `json:"type"` // 菜单类型: click 回复菜单
|
|
||||||
Click struct {
|
|
||||||
ID string `json:"id"` // 菜单ID, 不少于1字节, 不多于64字节
|
|
||||||
Content string `json:"content"` // 菜单显示内容, 不少于1字节, 不多于128字节
|
|
||||||
} `json:"click"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// MenuView 超链接菜单
|
|
||||||
type MenuView struct {
|
|
||||||
Type string `json:"type"` // 菜单类型: view 超链接菜单
|
|
||||||
View struct {
|
|
||||||
URL string `json:"url"` // 点击后跳转的链接, 不少于1字节, 不多于2048字节
|
|
||||||
Content string `json:"content"` // 菜单显示内容, 不少于1字节, 不多于1024字节
|
|
||||||
} `json:"view"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// MenuMiniProgram 小程序菜单
|
|
||||||
type MenuMiniProgram struct {
|
|
||||||
Type string `json:"type"` // 菜单类型: miniprogram 小程序菜单
|
|
||||||
MiniProgram struct {
|
|
||||||
AppID string `json:"appid"` // 小程序appid, 不少于1字节, 不多于32字节
|
|
||||||
PagePath string `json:"pagepath"` // 点击后进入的小程序页面, 不少于1字节, 不多于1024字节
|
|
||||||
Content string `json:"content"` // 菜单显示内容, 不少于1字节, 不多于1024字节
|
|
||||||
} `json:"miniprogram"`
|
|
||||||
}
|
|
||||||
@@ -19,7 +19,7 @@ const (
|
|||||||
// ReceptionistOptions 添加接待人员请求参数
|
// ReceptionistOptions 添加接待人员请求参数
|
||||||
type ReceptionistOptions struct {
|
type ReceptionistOptions struct {
|
||||||
OpenKFID string `json:"open_kfid"` // 客服帐号ID
|
OpenKFID string `json:"open_kfid"` // 客服帐号ID
|
||||||
UserIDList []string `json:"userid_list"` // 接待人员userid列表。第三方应用填密文userid,即open_userid 可填充个数:1 ~ 100。超过100个需分批调用。
|
UserIDList []string `json:"userid_list"` // 接待人员userid列表
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReceptionistSchema 添加接待人员响应内容
|
// ReceptionistSchema 添加接待人员响应内容
|
||||||
|
|||||||
@@ -28,11 +28,11 @@ type ServiceStateGetSchema struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ServiceStateGet 获取会话状态
|
// ServiceStateGet 获取会话状态
|
||||||
// 0 未处理 新会话接入(客户发信咨询)。可选择:1.直接用API自动回复消息。2.放进待接入池等待接待人员接待。3.指定接待人员(接待人员须处于“正在接待”中,下同)进行接待
|
//0 未处理 新会话接入。可选择:1.直接用API自动回复消息。2.放进待接入池等待接待人员接待。3.指定接待人员进行接待
|
||||||
// 1 由智能助手接待 可使用API回复消息。可选择转入待接入池或者指定接待人员处理
|
//1 由智能助手接待 可使用API回复消息。可选择转入待接入池或者指定接待人员处理。
|
||||||
//2 待接入池排队中 在待接入池中排队等待接待人员接入。可选择转为指定人员接待
|
//2 待接入池排队中 在待接入池中排队等待接待人员接入。可选择转为指定人员接待
|
||||||
// 3 由人工接待 人工接待中。可选择转接给其他接待人员处理或者结束会话
|
//3 由人工接待 人工接待中。可选择结束会话
|
||||||
// 4 已结束 会话已经结束或未开始。不允许变更会话状态,客户重新发信咨询后会话状态变为“未处理”
|
//4 已结束 会话已经结束或未开始。不允许变更会话状态,等待用户发起咨询
|
||||||
// 注:一个微信用户向一个客服帐号发起咨询后,在48h内,或主动结束会话前(包括接待人员手动结束,或企业通过API结束会话),都算是一次会话
|
// 注:一个微信用户向一个客服帐号发起咨询后,在48h内,或主动结束会话前(包括接待人员手动结束,或企业通过API结束会话),都算是一次会话
|
||||||
func (r *Client) ServiceStateGet(options ServiceStateGetOptions) (info ServiceStateGetSchema, err error) {
|
func (r *Client) ServiceStateGet(options ServiceStateGetOptions) (info ServiceStateGetSchema, err error) {
|
||||||
var (
|
var (
|
||||||
@@ -64,14 +64,8 @@ type ServiceStateTransOptions struct {
|
|||||||
ServicerUserID string `json:"servicer_userid"` // 接待人员的userid,当state=3时要求必填,接待人员须处于“正在接待”中
|
ServicerUserID string `json:"servicer_userid"` // 接待人员的userid,当state=3时要求必填,接待人员须处于“正在接待”中
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServiceStateTransSchema 变更会话状态响应内容
|
|
||||||
type ServiceStateTransSchema struct {
|
|
||||||
util.CommonError
|
|
||||||
MsgCode string `json:"msg_code"` // 用于发送响应事件消息的code,将会话初次变更为service_state为2和3时,返回回复语code,service_state为4时,返回结束语code。可用该code调用发送事件响应消息接口给客户发送事件响应消息
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServiceStateTrans 变更会话状态
|
// ServiceStateTrans 变更会话状态
|
||||||
func (r *Client) ServiceStateTrans(options ServiceStateTransOptions) (info ServiceStateTransSchema, err error) {
|
func (r *Client) ServiceStateTrans(options ServiceStateTransOptions) (info util.CommonError, err error) {
|
||||||
var (
|
var (
|
||||||
accessToken string
|
accessToken string
|
||||||
data []byte
|
data []byte
|
||||||
|
|||||||
@@ -120,7 +120,6 @@ type EnterSessionEvent struct {
|
|||||||
ExternalUserID string `json:"external_userid"` // 客户UserID
|
ExternalUserID string `json:"external_userid"` // 客户UserID
|
||||||
Scene string `json:"scene"` // 进入会话的场景值,获取客服帐号链接开发者自定义的场景值
|
Scene string `json:"scene"` // 进入会话的场景值,获取客服帐号链接开发者自定义的场景值
|
||||||
SceneParam string `json:"scene_param"` // 进入会话的自定义参数,获取客服帐号链接返回的url,开发者按规范拼接的scene_param参数
|
SceneParam string `json:"scene_param"` // 进入会话的自定义参数,获取客服帐号链接返回的url,开发者按规范拼接的scene_param参数
|
||||||
WelcomeCode string `json:"welcome_code"` // 如果满足发送欢迎语条件(条件为:1. 企业没有在管理端配置了原生欢迎语;2. 用户在过去48小时里未收过欢迎语,且未向该用户发过消息),会返回该字段。可用该welcome_code调用发送事件响应消息接口给客户发送欢迎语。
|
|
||||||
} `json:"event"` // 事件消息
|
} `json:"event"` // 事件消息
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,7 +143,6 @@ type ReceptionistStatusChangeEvent struct {
|
|||||||
Event struct {
|
Event struct {
|
||||||
EventType string `json:"event_type"` // 事件类型。此处固定为:servicer_status_change
|
EventType string `json:"event_type"` // 事件类型。此处固定为:servicer_status_change
|
||||||
ReceptionistUserID string `json:"servicer_userid"` // 客服人员userid
|
ReceptionistUserID string `json:"servicer_userid"` // 客服人员userid
|
||||||
OpenKFID string `json:"open_kfid"` // 客服帐号ID
|
|
||||||
Status uint32 `json:"status"` // 状态类型。1-接待中 2-停止接待
|
Status uint32 `json:"status"` // 状态类型。1-接待中 2-停止接待
|
||||||
} `json:"event"`
|
} `json:"event"`
|
||||||
}
|
}
|
||||||
@@ -160,6 +158,5 @@ type SessionStatusChangeEvent struct {
|
|||||||
ChangeType uint32 `json:"change_type"` // 变更类型。1-从接待池接入会话 2-转接会话 3-结束会话
|
ChangeType uint32 `json:"change_type"` // 变更类型。1-从接待池接入会话 2-转接会话 3-结束会话
|
||||||
OldReceptionistUserID string `json:"old_servicer_userid"` // 老的客服人员userid。仅change_type为2和3有值
|
OldReceptionistUserID string `json:"old_servicer_userid"` // 老的客服人员userid。仅change_type为2和3有值
|
||||||
NewReceptionistUserID string `json:"new_servicer_userid"` // 新的客服人员userid。仅change_type为1和2有值
|
NewReceptionistUserID string `json:"new_servicer_userid"` // 新的客服人员userid。仅change_type为1和2有值
|
||||||
MsgCode string `json:"msg_code"` // 用于发送事件响应消息的code,仅change_type为1和3时,会返回该字段。可用该msg_code调用发送事件响应消息接口给客户发送回复语或结束语。
|
|
||||||
} `json:"event"` // 事件消息
|
} `json:"event"` // 事件消息
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ type Message struct {
|
|||||||
MsgID string `json:"msgid"` // 消息ID
|
MsgID string `json:"msgid"` // 消息ID
|
||||||
OpenKFID string `json:"open_kfid"` // 客服帐号ID
|
OpenKFID string `json:"open_kfid"` // 客服帐号ID
|
||||||
ExternalUserID string `json:"external_userid"` // 客户UserID
|
ExternalUserID string `json:"external_userid"` // 客户UserID
|
||||||
ReceptionistUserID string `json:"servicer_userid"` // 接待客服userID
|
|
||||||
SendTime uint64 `json:"send_time"` // 消息发送时间
|
SendTime uint64 `json:"send_time"` // 消息发送时间
|
||||||
Origin uint32 `json:"origin"` // 消息来源。3-客户回复的消息 4-系统推送的消 息
|
Origin uint32 `json:"origin"` // 消息来源。3-客户回复的消息 4-系统推送的消 息
|
||||||
MsgType string `json:"msgtype"` // 消息类型
|
MsgType string `json:"msgtype"` // 消息类型
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
企业微信会话存档SDK(基于企业微信C版官方SDK封装),暂时只支持在`linux`环境下使用当前SDK。
|
企业微信会话存档SDK(基于企业微信C版官方SDK封装),暂时只支持在`linux`环境下使用当前SDK。
|
||||||
|
|
||||||
|
|
||||||
### 官方文档地址
|
### 官方文档地址
|
||||||
https://open.work.weixin.qq.com/api/doc/90000/90135/91774
|
https://open.work.weixin.qq.com/api/doc/90000/90135/91774
|
||||||
|
|
||||||
@@ -11,10 +10,6 @@ https://open.work.weixin.qq.com/api/doc/90000/90135/91774
|
|||||||
|
|
||||||
2、从 `github.com/silenceper/wechat/v2/work/msgaudit/lib` 文件夹下复制 `libWeWorkFinanceSdk_C.so` 动态库文件到系统动态链接库默认文件夹下,或者复制到任意文件夹并在当前文件夹下执行 `export LD_LIBRARY_PATH=$(pwd)`命令设置动态链接库检索地址后即可正常使用
|
2、从 `github.com/silenceper/wechat/v2/work/msgaudit/lib` 文件夹下复制 `libWeWorkFinanceSdk_C.so` 动态库文件到系统动态链接库默认文件夹下,或者复制到任意文件夹并在当前文件夹下执行 `export LD_LIBRARY_PATH=$(pwd)`命令设置动态链接库检索地址后即可正常使用
|
||||||
|
|
||||||
3、编译要求
|
|
||||||
- 开启CGO: `CGO_ENABLED=1`
|
|
||||||
- 增加tags参数`msgaudit`: `go build -tags msgaudit`或者`go run -tags msgaudit main.go`
|
|
||||||
|
|
||||||
### Example
|
### Example
|
||||||
|
|
||||||
```go
|
```go
|
||||||
@@ -35,7 +30,7 @@ func main() {
|
|||||||
//初始化客户端
|
//初始化客户端
|
||||||
wechatClient := wechat.NewWechat()
|
wechatClient := wechat.NewWechat()
|
||||||
|
|
||||||
workClient := wechatClient.GetWork(&config.Config{
|
workClient := wechatClient.NewWork(&config.Config{
|
||||||
CorpID: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
|
CorpID: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||||
CorpSecret: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
|
CorpSecret: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||||
RasPrivateKey: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
|
RasPrivateKey: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||||
@@ -64,14 +59,13 @@ func main() {
|
|||||||
|
|
||||||
if chatInfo.Type == "image" {
|
if chatInfo.Type == "image" {
|
||||||
image, _ := chatInfo.GetImageMessage()
|
image, _ := chatInfo.GetImageMessage()
|
||||||
sdkFileID := image.Image.SdkFileID
|
sdkfileid := image.Image.SdkFileId
|
||||||
|
|
||||||
isFinish := false
|
isFinish := false
|
||||||
buffer := bytes.Buffer{}
|
buffer := bytes.Buffer{}
|
||||||
indexBuf := ""
|
|
||||||
for !isFinish {
|
for !isFinish {
|
||||||
//获取媒体数据
|
//获取媒体数据
|
||||||
mediaData, err := client.GetMediaData(indexBuf, sdkFileID, "", "", 5)
|
mediaData, err := client.GetMediaData("", sdkfileid, "", "", 5)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("媒体数据拉取失败:%v \n", err)
|
fmt.Printf("媒体数据拉取失败:%v \n", err)
|
||||||
return
|
return
|
||||||
@@ -80,7 +74,6 @@ func main() {
|
|||||||
if mediaData.IsFinish {
|
if mediaData.IsFinish {
|
||||||
isFinish = mediaData.IsFinish
|
isFinish = mediaData.IsFinish
|
||||||
}
|
}
|
||||||
indexBuf = mediaData.OutIndexBuf
|
|
||||||
}
|
}
|
||||||
filePath, _ := os.Getwd()
|
filePath, _ := os.Getwd()
|
||||||
filePath = path.Join(filePath, "test.png")
|
filePath = path.Join(filePath, "test.png")
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
//go:build linux && cgo && msgaudit
|
// +build linux
|
||||||
// +build linux,cgo,msgaudit
|
|
||||||
|
|
||||||
//Package msgaudit only for linux
|
//Package msgaudit only for linux
|
||||||
package msgaudit
|
package msgaudit
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
//go:build !linux || !cgo || !msgaudit
|
// +build !linux linux,!cgo
|
||||||
// +build !linux !cgo !msgaudit
|
|
||||||
|
|
||||||
//Package msgaudit for unsupport platform
|
//Package msgaudit for unsupport platform
|
||||||
package msgaudit
|
package msgaudit
|
||||||
|
|||||||
Reference in New Issue
Block a user