1
0
mirror of https://github.com/silenceper/wechat.git synced 2026-02-07 22:22:28 +08:00

Compare commits

..

4 Commits

Author SHA1 Message Date
silenceper
39dbfd1c13 fix linux build failed when cgo disable (#453) 2021-08-30 18:00:08 +08:00
silenceper
5ad3475cdb 修正字段问题 (#451)
fix #443
2021-08-30 15:13:35 +08:00
silenceper
bcc41989ed Update go.yml (#452) 2021-08-30 15:13:09 +08:00
silenceper
e189b87e71 merge branch release-2.0 to v2 (#450)
* feat: add/delete subscribe template (#449)

* feat: 添加 SDKApiNotOpen 错误信息 (#448)

* 添加微信客服SDK

* polish:优化签名函数

* polish:优化注释内容

* polish:复用已有的Token以及CommonError,移除无用的输出

* polish:复用已有的消息加解密

* fix:修复错误信息被覆盖的问题

* polish:go fmt 文件

* polish:客服链接支持自定义参数并更新注释文档内容

* feat:支持微信客服回调请求的校验和消息的解析,复用原有的Signature和DecryptMsg方法

* feat:对外暴露SDKApiForbidden等错误

可以通过调用升级服务相关接口然后根据该错误判断微信客服配置来源

* feat:添加无效的open_kfid错误信息

* fix: 添加SDKApiNotOpen 错误信息

目前主要用于判断客户是否关闭了API授权,如果客户关闭了API功能导致服务异常,则可以引导用户执行相应的操作重新开启改功能

Co-authored-by: Afeyer <afeyer@h5base.cn>

Co-authored-by: ZmJ <wzmmmmj@gmail.com>
Co-authored-by: Afeyer <1500527791@qq.com>
Co-authored-by: Afeyer <afeyer@h5base.cn>
2021-08-30 11:14:25 +08:00
117 changed files with 867 additions and 2528 deletions

View File

@@ -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 }}

View File

@@ -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:'

View File

@@ -2,20 +2,16 @@
![Go](https://github.com/silenceper/wechat/workflows/Go/badge.svg?branch=release-2.0) ![Go](https://github.com/silenceper/wechat/workflows/Go/badge.svg?branch=release-2.0)
[![Go Report Card](https://goreportcard.com/badge/github.com/silenceper/wechat)](https://goreportcard.com/report/github.com/silenceper/wechat) [![Go Report Card](https://goreportcard.com/badge/github.com/silenceper/wechat)](https://goreportcard.com/report/github.com/silenceper/wechat)
[![pkg](https://img.shields.io/badge/dev-reference-007d9c?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/silenceper/wechat/v2?tab=doc) [![pkg](https://img.shields.io/badge/dev-reference-007d9c?logo=go&logoColor=white&style=flat)](https://pkg.go.dev/github.com/silenceper/wechat/v2?tab=doc)
![version](https://img.shields.io/badge/version-v2-green)
使用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
View File

@@ -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 {

View File

@@ -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})

View File

@@ -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方法

View File

@@ -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)

View File

@@ -1,2 +0,0 @@
# 智能对话
TODO

View File

@@ -1,2 +0,0 @@
# 小游戏
TODO

View File

@@ -1,2 +0,0 @@
# 小程序
TODO

View File

@@ -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)
说明:「发表记录」包括群发和发布。
注意:该接口,只能处理 "发布" 相关的信息,无法操作和获取 "群发" 相关内容!![官方回复](https://developers.weixin.qq.com/community/develop/doc/0002a4fb2109d8f7a91d421c556c00)
- 群发:主动推送给粉丝,历史消息可看,被搜一搜收录,可以限定部分的粉丝接收到。
- 发布:不会主动推给粉丝,历史消息列表看不到,但是是公开给所有人的文章。也不会占用群发的次数。每天可以发布多篇内容。可以用于自动回复、自定义菜单、页面模板和话题中,发布成功时会生成一个永久链接。
| 名称 | 请求方式 | 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 |
## 图文消息留言管理
## 用户管理
## 账号管理
## 数据统计
## 微信卡券
## 微信门店
## 智能接口
## 微信设备功能
## 微信“一物一码”
## 微信发票
## 微信非税缴费

View File

@@ -1 +0,0 @@
# 开放平台

View File

@@ -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

View File

@@ -1,2 +0,0 @@
# 微信支付
TODO

2
go.mod
View File

@@ -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
View File

@@ -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=

View File

@@ -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
}

View File

@@ -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

View File

@@ -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

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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 请求并返回二维码二进制数据

View File

@@ -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,
})
}

View File

@@ -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
}

View File

@@ -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

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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"`

View File

@@ -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)

View File

@@ -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

View File

@@ -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 {

View File

@@ -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"`

View File

@@ -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
}

View File

@@ -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 != "" {

View File

@@ -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

View File

@@ -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
} }

View File

@@ -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
} }
}

View File

@@ -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
}

View File

@@ -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

View File

@@ -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"`
} }

View File

@@ -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
}

View File

@@ -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"`
}

View File

@@ -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 添加接待人员响应内容

View File

@@ -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时返回回复语codeservice_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

View File

@@ -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"` // 事件消息
} }

View File

@@ -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"` // 消息类型

View File

@@ -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")

View File

@@ -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

View File

@@ -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