@pawastation/wechat-kf 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.zh-CN.md CHANGED
@@ -6,401 +6,226 @@
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
7
7
  [![OpenClaw](https://img.shields.io/badge/OpenClaw-channel%20plugin-blue.svg)](https://openclaw.dev)
8
8
 
9
- **企业微信客服渠道插件** — 让微信用户通过企业微信客服 API 与你的 OpenClaw AI Agent 对话。零运行时依赖,仅使用 Node.js 内置模块。
9
+ 让微信用户通过**微信客服**与你的 OpenClaw AI Agent 对话。
10
10
 
11
11
  ---
12
12
 
13
- ## 功能特性
14
-
15
- - **入站消息处理** — 接收文本、图片、语音、视频、文件、位置、链接、小程序、视频号、视频号商品、视频号订单、笔记、名片、合并转发消息等 14+ 种消息类型
16
- - **事件处理** — 处理 enter_session(用户进入会话)、msg_send_fail(消息发送失败)、servicer_status_change(接待人员状态变更)事件
17
- - **丰富的出站消息** — 发送文本、图片、语音、视频、文件、链接、位置、小程序、菜单、名片和视频号文章消息
18
- - **媒体上传与下载** 自动下载入站媒体(图片、语音、视频、文件),通过企业微信临时素材 API 上传出站媒体;通过框架 loadWebMedia 支持所有 URL 格式(HTTP、file://、本地路径)
19
- - **Markdown 转 Unicode 格式化** — 将 Markdown 粗体/斜体/标题/列表转换为 Unicode 数学字母符号,在微信中实现富文本效果
20
- - **AES-256-CBC 加密** — 完整的微信回调加密/解密,包含 SHA-1 签名验证和 PKCS#7 填充校验
21
- - **Webhook + 轮询兜底** — webhook 处理器注册在框架共享网关上接收实时回调,同时提供 30 秒轮询兜底机制保证可靠性
22
- - **动态客服账号发现** — 客服账号 ID(open_kfid)从 webhook 回调中自动发现,支持启用/禁用/删除生命周期管理
23
- - **基于游标的增量同步** — 每个客服账号独立持久化同步游标,使用原子文件写入保证崩溃安全
24
- - **Access Token 自动缓存** — Token 在内存中以哈希键缓存,过期前 5 分钟自动刷新,Token 过期时自动重试
25
- - **多客服账号隔离** — 每个客服账号拥有独立的会话、游标和路由上下文,通过 per-kfId 互斥锁隔离处理
26
- - **DM 策略控制** — 可配置的访问控制模式:`open`(开放)、`allowlist`(白名单)或 `pairing`(配对),包含安全适配器
27
- - **文本分块** — 自动按微信 2000 字符消息限制拆分长回复,并声明 chunker 供框架集成
28
- - **会话限制感知** 检测并优雅处理微信 48 小时回复窗口和 5 条消息限制
29
- - **竞态条件安全** per-kfId 互斥锁和 msgid 去重,防止消息重复处理
30
- - **仿真回复延迟** 可配置的打字延迟模拟,营造自然的对话节奏
31
- - **优雅关停** 响应中止信号,带前置检查守卫,干净地停止轮询循环
13
+ ## 功能介绍
14
+
15
+ ### 可接收的消息类型
16
+
17
+ | 消息类型 | 说明 |
18
+ | ---------- | ---------------------------------------------------- |
19
+ | 文本 | 普通文字,含菜单回调 |
20
+ | 图片 | 图片附件 |
21
+ | 语音 | AMR 格式语音 |
22
+ | 视频 | 视频附件 |
23
+ | 文件 | 各类文件附件 |
24
+ | 链接 | 分享的链接卡片 |
25
+ | 小程序 | 小程序消息卡片 |
26
+ | 位置 | 地理位置(含坐标) |
27
+ | 合并转发 | 多条消息合并转发 |
28
+ | 视频号商品 | 视频号商品卡片 |
29
+ | 视频号订单 | 视频号订单消息 |
30
+ | 视频号消息 | 视频号动态、直播或名片,仅返回部分字段(昵称、标题) |
31
+ | 用户笔记 | 仅识别类型,API 不支持读取内容 |
32
+
33
+ ### 可发送的消息类型
34
+
35
+ | 消息类型 | 说明 |
36
+ | ---------- | ---------------- |
37
+ | 文本 | 普通文字 |
38
+ | 图片 | 图片附件 |
39
+ | 语音 | AMR 语音 |
40
+ | 视频 | 视频附件 |
41
+ | 文件 | 各类文件 |
42
+ | 链接卡片 | 带封面图的富链接 |
43
+ | 小程序卡片 | 小程序跳转卡片 |
44
+ | 菜单 | 快捷回复菜单按钮 |
45
+ | 名片 | 企业员工名片 |
46
+ | 位置 | 地理位置 |
47
+ | 获客链接 | 企业获客链接卡片 |
48
+
49
+ ### 微信特色功能
50
+
51
+ - **Markdown 样式**:Agent 回复中的粗体、列表、标题等 Markdown 格式,自动转换为微信可显示的 Unicode 样式(粗体用 𝗯𝗼𝗹𝗱 字体,列表用符号等):
52
+
53
+ ![在微信里显示粗体和列表等 Markdown 样式](docs/images/wechat-markdown-style.png)
54
+
55
+ - **消息防抖合并**(`debounceMs`):用户连续快速发出多条消息时,插件会等待设定的时间窗口内无新消息后,再将所有消息合并一次性发给 Agent,避免 Agent 对同一用户意图重复响应。
32
56
 
33
- ## 前提条件
34
-
35
- 1. 一个**企业微信账号**,且拥有管理员权限 — [注册地址](https://work.weixin.qq.com/)
36
- 2. 至少一个**客服账号**(在企业微信的「微信客服」模块中创建)
37
- 3. 一个**公网可访问的 URL**,用于接收回调 — 可使用 [Tailscale Funnel](https://docs.openclaw.ai/gateway/tailscale#tailscale)(推荐,OpenClaw Gateway 内置支持)、[ngrok](https://ngrok.com/)、[Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/) 或有公网 IP 的服务器
38
- 4. 已安装并运行 **OpenClaw Gateway**(`openclaw gateway start`)
39
-
40
- 微信客服 API 有**两种接入方式**,请根据实际情况选择:
57
+ ---
41
58
 
42
- | | 方式一:企业微信后台自建应用 | 方式二:微信客服后台 API 托管 |
43
- | ----------------- | ----------------------------------------------------------------- | -------------------------------------------------- |
44
- | **管理入口** | [企业微信管理后台](https://work.weixin.qq.com/wework_admin/frame) | [微信客服管理后台](https://work.weixin.qq.com/kf/) |
45
- | **Secret 来源** | 自建应用的 Secret(应用密钥) | 微信客服专属 Secret(开发配置中查看) |
46
- | **回调配置位置** | 企业微信后台 → 微信客服 → API → 回调设置 | 微信客服后台 → 开发配置 → 回调设置 |
47
- | **回调 URL 要求** | 必须使用经过企业认证的域名(需在企业微信后台完成可信域名配置) | 无此限制,任意公网可访问的 URL 即可 |
48
- | **需要自建应用** | 是 — 需创建应用并关联微信客服权限 | 否 — 直接在微信客服后台配置 |
49
- | **IP 白名单** | 在自建应用中配置「企业可信 IP」 | 不需要(微信客服后台无此限制) |
50
- | **适用场景** | 已有企业微信自建应用、需与其他企微功能集成 | 仅需微信客服能力、追求简单快速接入 |
51
- | **推荐程度** | 功能更完整,适合复杂场景 | 配置更简单,适合快速上手 |
59
+ ## 为什么选微信客服 API
52
60
 
53
- > **重要:** 两种方式是**互斥关系** — 同一个客服账号只能通过其中一种方式管理,不能同时使用。选定后如需切换,需要先解除当前方式的 API 绑定。
61
+ 相比其他接入微信的方式,微信客服(WeChat KF API)有几个明显优势:
54
62
 
55
- ## 安装
63
+ 1. **不需要关注公众号** — 用户可以直接点链接发起对话,无需先关注任何账号(后续也可在客服消息中找回历史对话)
64
+ 2. **可以发起主动消息** — 在 48 小时会话窗口内,你可以主动给用户发消息(最多五次,但用户恢复后即重置)
65
+ 3. **支持多种消息类型** — 文本、图片、语音、视频、文件、链接卡片、小程序卡片、菜单等
66
+ 4. **接入简单** — 不需要认证域名,不需要 IP 白名单,用普通 Tunnel 公网地址就能跑
67
+ 5. **个人就能注册** — 不需要企业资质,用企业微信 App 就能注册一个"企业"并创建客服账号
68
+ 6. **免费** — 微信客服 API 本身免费,无需公众号认证或服务号
56
69
 
57
- ```bash
58
- openclaw plugins install @pawastation/wechat-kf
59
- ```
70
+ ---
60
71
 
61
- ## 企业微信客服接入指南
72
+ ## 前提条件
62
73
 
63
- 两种接入方式共享相同的底层 API(`sync_msg`、`send_msg` 等),区别仅在于凭证获取方式和管理入口不同。本插件对两种方式**完全兼容**。
74
+ 1. **企业微信账号** — 用企业微信 App 注册即可(个人就能注册,不需要真实企业)
75
+ 2. **已安装并运行 OpenClaw** — 参考 [OpenClaw 文档](https://docs.openclaw.ai/)
64
76
 
65
77
  ---
66
78
 
67
- ### 方式一:企业微信后台自建应用
79
+ ## 快速开始
68
80
 
69
- 通过企业微信管理后台创建自建应用,然后将该应用与微信客服 API 关联。这是功能最完整的接入方式。
81
+ ### 1 步:安装插件
70
82
 
71
- #### 第 1 步:获取企业 ID(Corp ID)
83
+ ```bash
84
+ openclaw plugins install @pawastation/wechat-kf
85
+ ```
72
86
 
73
- 1. 登录[企业微信管理后台](https://work.weixin.qq.com/wework_admin/frame)
74
- 2. 点击左侧菜单最下方的**「我的企业」**
75
- 3. 在页面底部找到并复制**「企业ID」** — 格式如 `wwXXXXXXXXXXXXXXXX`
87
+ ### 第 2 步:安装 Tunnel(cloudflared)
76
88
 
77
- #### 2 步:创建自建应用并获取 Secret
89
+ 微信客服需要一个公网可访问的回调地址。推荐使用 Cloudflare Tunnel:
78
90
 
79
- 1. 进入**「应用管理」→「自建」**
80
- 2. 点击**「创建应用」**(或使用已有应用)
81
- 3. 进入应用详情页,获取**「Secret」**(应用密钥)
91
+ ```bash
92
+ # macOS
93
+ brew install cloudflare/cloudflare/cloudflared
82
94
 
83
- > 每个自建应用有独立的 Secret。调用微信客服 API 时,需要使用与微信客服关联的应用 Secret 来获取 access_token。
95
+ # 启动 tunnel(把 7860 换成你的 OpenClaw Gateway 端口)
96
+ cloudflared tunnel --url http://localhost:7860
97
+ ```
84
98
 
85
- #### 3 步:开启微信客服 API 并关联自建应用
99
+ 启动后会显示一个 `https://xxxx.trycloudflare.com` 地址,记下来后面要用。
86
100
 
87
- 1. 回到**「应用管理」页面**,进入**「微信客服」**
88
- 2. 点击**「API」**小按钮
89
- 3. 在**「可调用接口的应用」**中选择你在第 2 步创建的自建应用
90
- 4. 在**「通过 API 管理微信客服账号 → 企业内部开发」**中,勾选需要通过 API 管理的客服账号
101
+ > 也可以使用 Tailscale Funnel、ngrok 等服务来生成公网地址。
91
102
 
92
- > 开启后,被选中账号的所有消息与事件都将通过回调推送给你的应用,原有的原生接待规则将暂不生效。
103
+ ### 第 3 步:注册企业微信
93
104
 
94
- #### 第 4 步:配置回调地址(Callback URL)
105
+ 如果你还没有企业微信账号:
95
106
 
96
- 1. 在微信客服的 API 设置页面,找到**「回调设置」**
97
- 2. 设置**回调地址(URL)**:
98
- ```
99
- https://your-domain.com/wechat-kf
100
- ```
101
- > 使用你的公网 URL。企业微信回调 URL 必须使用经过企业认证的域名(需在企业微信后台完成可信域名配置)。
102
- 3. 设置 **Token** — 任意随机字符串(英文或数字,不超过 32 位),或点击**「随机获取」**自动生成
103
- 4. 设置 **EncodingAESKey** — 43 位字符串(英文或数字),或点击**「随机获取」**自动生成
104
- 5. 点击**「保存」** — 企业微信会发送一个 GET 验证请求到你的回调地址
107
+ 1. 下载**企业微信** App
108
+ 2. 菜单点击 → 创建/加入企业
109
+ 3. 选择**其他**(按实际情况填写表单提交)
105
110
 
106
- > **注意:** 保存回调配置前,webhook 服务必须已经在运行,否则验证会失败。请先启动 OpenClaw Gateway(参考[验证步骤](#验证))。
111
+ ![企业微信 App 菜单里选"创建/加入企业"然后选其他](docs/images/wecom-app-create-enterprise.png)
107
112
 
108
- > **重要:** 企业微信后台自建应用的回调 URL 必须使用经过企业认证的域名(需在企业微信后台完成可信域名配置),而微信客服后台的 API 托管方式没有此限制,任意公网可访问的 URL 即可。如果你没有经过认证的域名,建议使用方式二。
113
+ ### 4 步:创建客服账号
109
114
 
110
- #### 5 步:配置 IP 白名单
115
+ 1. 打开[微信客服管理后台](https://kf.weixin.qq.com/)(用企业微信 App 扫码登录)
116
+ 2. 创建一个客服账号(设置名称和头像)
111
117
 
112
- 1. 在自建应用设置中,进入**「企业可信IP」**或**「IP 白名单」**
113
- 2. 添加你服务器的公网 IP 地址
114
- 3. 查看当前公网 IP:`curl -s https://api.ipify.org`
118
+ ![在微信客服后台创建客服账号](docs/images/kf-create-account.png)
115
119
 
116
- > **注意:** 如果你的公网 IP 发生变化(家庭宽带常见),API 调用会因认证失败而报错。请注意监控 IP 变化并及时更新白名单。
120
+ 同时在这里复制**企业 ID**(Corp ID)备用:
117
121
 
118
- #### 6 步:创建客服账号
122
+ ![复制企业 ID(Corp ID)](docs/images/kf-corp-id.png)
119
123
 
120
- 1. 在**「微信客服」**页面,点击**「添加客服账号」**
121
- 2. 配置客服名称、头像等信息
122
- 3. 记录 **open_kfid** — 格式如 `wkXXXXXXXXXXXXXXXX`
123
- 4. 生成**「客服链接」**分享给用户 — 微信用户通过此链接发起咨询
124
+ ### 第 5 步:在微信客服后台配置回调
124
125
 
125
- > 你不需要在 OpenClaw 配置中填写 open_kfid。插件会自动从 webhook 事件中发现客服账号。
126
+ 1. 打开[微信客服管理后台](https://kf.weixin.qq.com/) **开发配置** **开始使用**
127
+ 2. 填入回调地址(你的 Tunnel 地址 + `/wechat-kf`,例如 `https://xxxx.trycloudflare.com/wechat-kf`)
128
+ 3. 点击**「随机获取」**生成 Token 和 EncodingAESKey
126
129
 
127
- ---
130
+ ![输入 Tunnel 公网回调地址,并生成 Token 和 EncodingAESKey](docs/images/kf-callback-config.png)
128
131
 
129
- ### 方式二:微信客服后台 API 托管
132
+ **先复制好 Token EncodingAESKey,暂时不要点「保存」** — 下一步先把这两个值填入 OpenClaw,再回来保存验证。
130
133
 
131
- 通过[微信客服管理后台](https://work.weixin.qq.com/kf/)直接启用 API,无需创建企业微信自建应用。配置更简单,适合只需要微信客服功能的场景。
134
+ ### 第 6 步:配置 OpenClaw 后台
132
135
 
133
- #### 1 步:获取企业 ID(Corp ID)
136
+ 进入 OpenClaw 后台,添加微信客服渠道配置。`appSecret` 先随便填一个占位值(第 8 步替换),把上一步生成的 Token 和 EncodingAESKey 填入对应字段,**保存配置**:
134
137
 
135
- 1. 登录[企业微信管理后台](https://work.weixin.qq.com/wework_admin/frame)
136
- 2. 点击**「我的企业」**,复制**「企业ID」** — 格式如 `wwXXXXXXXXXXXXXXXX`
138
+ ![OpenClaw 后台插件配置,输入四个值并开启 Enabled](docs/images/openclaw-channel-config.png)
137
139
 
138
- > Corp ID 始终从企业微信管理后台获取,两种方式相同。
140
+ OpenClaw 配置示例:
139
141
 
140
- #### 第 2 步:在微信客服后台启用 API
142
+ ```yaml
143
+ channels:
144
+ wechat-kf:
145
+ enabled: true
146
+ corpId: "wwXXXXXXXXXXXXXXXX" # 第 4 步复制的企业 ID
147
+ appSecret: "placeholder" # 先填占位,第 8 步替换
148
+ token: "从第 5 步复制"
149
+ encodingAESKey: "从第 5 步复制"
150
+ ```
141
151
 
142
- 1. 访问[微信客服管理后台](https://work.weixin.qq.com/kf/)(需管理员扫码登录)
143
- 2. 进入**「开发配置」**
144
- 3. 点击**「启用 API」**,按照指引填写回调配置
152
+ 保存后 OpenClaw 会立即开始监听回调地址,下一步微信的验证请求才能通过。
145
153
 
146
- ####3 步:获取微信客服 Secret
154
+ ###7 步:回到微信客服后台完成验证,获取 Secret
147
155
 
148
- 1. 启用 API 后,在**「开发配置」**页面查看并复制 **Secret**
149
- 2. 此 Secret 由企业微信团队下发给管理员,是**微信客服专属 Secret**,与自建应用 Secret 不同
150
- 3. 如未显示 Secret,点击查看/重置后复制
156
+ 回到[微信客服管理后台](https://kf.weixin.qq.com/)的开发配置页面,点击**「保存」** 微信会立即向你的回调地址发一个验证请求,OpenClaw 自动回应,验证通过后配置生效。
151
157
 
152
- > **重要区别:** 此处获取的 Secret 是「微信客服」专用密钥,而非自建应用密钥。使用此 Secret 获取的 access_token 仅可调用微信客服相关接口。
158
+ ![点击「开始使用」来配置收发微信消息的 OpenClaw 服务](docs/images/kf-start-dev-config.png)
153
159
 
154
- #### 4 步:配置回调地址(Callback URL)
160
+ 验证通过后,在同一页面复制 **App Secret**:
155
161
 
156
- 1. 在**「开发配置」**页面,找到回调配置区域
157
- 2. 设置**回调地址(URL)**:
158
- ```
159
- https://your-domain.com/wechat-kf
160
- ```
161
- 3. 设置 **Token** — 任意随机字符串,或点击**「随机获取」**自动生成
162
- 4. 设置 **EncodingAESKey** — 43 位字符串,或点击**「随机获取」**自动生成
163
- 5. 保存配置 — 系统会发送 GET 验证请求到回调地址
162
+ ![复制最后需要设置的 App Secret](docs/images/kf-copy-secret.png)
164
163
 
165
- > **注意:** 同样需要先启动 webhook 服务再保存配置,否则验证会失败。
164
+ ### 8 步:更新 Secret
166
165
 
167
- #### 5 步:创建客服账号
166
+ 把第 6 步填的占位 Secret 替换为刚复制的实际值,保存配置:
168
167
 
169
- 1. 在微信客服后台创建客服账号
170
- 2. 记录 **open_kfid**
171
- 3. 生成**「客服链接」**分享给用户
168
+ ![把随便输入的 Secret 替换为微信客服后台的实际值](docs/images/openclaw-update-secret.png)
172
169
 
173
- > 启用 API 后,该账号的所有消息和事件都将通过回调推送给你的服务,你需要及时通过 API 收发消息以保证正常服务。
170
+ ### 9 步:开始聊天
174
171
 
175
- ---
172
+ 在微信客服后台找到你的客服账号,复制**客服链接**分享给用户(或者自己先测试):
176
173
 
177
- ### 两种方式对照表
174
+ ![复制微信客服的访问链接](docs/images/kf-contact-link.png)
178
175
 
179
- | 对比项 | 方式一:企业微信后台自建应用 | 方式二:微信客服后台 API 托管 |
180
- | ----------------- | ------------------------------------------------------------------------------ | ---------------------------------------------------------------- |
181
- | **配置复杂度** | 较高 — 需创建应用、关联权限、配置白名单 | 较低 — 直接启用 API 即可 |
182
- | **Secret 类型** | 自建应用 Secret(应用密钥) | 微信客服专属 Secret |
183
- | **回调 URL 要求** | 必须使用经过企业认证的域名(需完成可信域名配置) | 无此限制,任意公网可访问的 URL 即可 |
184
- | **IP 白名单** | 必须配置(自建应用安全要求) | 无需配置 |
185
- | **API 能力** | 完整 — 可同时调用企业微信其他 API | 仅限微信客服相关接口 |
186
- | **管理灵活性** | 高 — 可精细控制哪些客服账号走 API | 中 — API 启用后覆盖所有账号 |
187
- | **与企微集成** | 天然集成 — 员工可在企微客户端接待 | 独立运作 — 不依赖企微客户端 |
188
- | **适合谁** | 已有企微开发经验、需要多功能集成的团队 | 只需 AI 客服、追求最快上手的开发者 |
189
- | **凭证获取** | 企业微信管理后台 → 应用管理 → 应用详情 | 微信客服管理后台 → 开发配置 |
190
- | **回调配置** | 企业微信后台 → 微信客服 → API → 回调设置 | 微信客服后台 → 开发配置 → 回调设置 |
191
- | **官方文档** | [企业微信开发者文档](https://developer.work.weixin.qq.com/document/path/94638) | [微信客服 API 文档](https://kf.weixin.qq.com/api/doc/path/93304) |
176
+ 用微信扫码或点开链接,就能和你的 AI Agent 对话了。
192
177
 
193
- > **本插件对两种方式完全兼容** 无论你使用哪种方式获取的 `corpId`、`appSecret`(Secret)、`token`、`encodingAESKey`,填入 OpenClaw 配置即可正常工作。配置字段 `appSecret` 既可以填自建应用 Secret,也可以填微信客服专属 Secret。
178
+ ### 10 步:安全设置(推荐)
194
179
 
195
- ## 配置
180
+ 默认情况下,任何拿到客服链接的人都可以发消息。如果你想限制访问,可以开启 `pairing`(配对)模式:
196
181
 
197
- 将以下内容添加到你的 OpenClaw 配置文件(`~/.openclaw/openclaw.yaml` 或通过 `openclaw config` 命令):
182
+ OpenClaw 后台把 `dmPolicy` 改为 `pairing`:
198
183
 
199
- ```yaml
200
- channels:
201
- wechat-kf:
202
- enabled: true
203
- corpId: "wwXXXXXXXXXXXXXXXX" # 企业 ID
204
- appSecret: "your-app-secret-here" # 应用密钥(自建应用 Secret 或微信客服 Secret)
205
- token: "your-callback-token" # 回调 Token
206
- encodingAESKey: "your-43-char-key" # 回调 EncodingAESKey(43 位字符)
207
- webhookPath: "/wechat-kf" # Webhook URL 路径(默认:/wechat-kf)
208
- dmPolicy: "open" # 访问控制:open | allowlist | pairing | disabled
209
- # allowFrom: # 仅在 dmPolicy 为 allowlist 时使用
210
- # - "external_userid_1"
211
- # - "external_userid_2"
212
- ```
184
+ ![在 OpenClaw 后台修改微信渠道的认证方式](docs/images/openclaw-dm-policy.png)
213
185
 
214
- ### 配置字段说明
215
-
216
- | 字段 | 类型 | 必填 | 默认值 | 说明 |
217
- | ---------------- | -------- | ------ | ------------ | ------------------------------------------------------------- |
218
- | `enabled` | boolean | 否 | `false` | 是否启用该渠道 |
219
- | `corpId` | string | **是** | — | 企业 ID |
220
- | `appSecret` | string | **是** | — | 自建应用密钥或微信客服 Secret |
221
- | `token` | string | **是** | — | Webhook 回调 Token |
222
- | `encodingAESKey` | string | **是** | — | 43 位 AES 加密密钥 |
223
- | `webhookPath` | string | 否 | `/wechat-kf` | Webhook 回调 URL 路径 |
224
- | `dmPolicy` | string | 否 | `"open"` | `open`(开放)/ `allowlist`(白名单)/ `pairing`(配对)/ `disabled`(禁用) |
225
- | `allowFrom` | string[] | 否 | `[]` | 允许的 external_userid 列表(dmPolicy 为 `allowlist` 时使用) |
226
-
227
- ## 验证
228
-
229
- 1. 启动 OpenClaw Gateway:
230
- ```bash
231
- openclaw gateway start
232
- ```
233
- 2. 将 Gateway 暴露到公网(如果不在公网服务器上)。方式 A — Tailscale Funnel(内置支持):
234
- ```bash
235
- openclaw gateway --tailscale funnel --auth password
236
- ```
237
- 方式 B — ngrok:
238
- ```bash
239
- ngrok http <gateway-port>
240
- ```
241
- 3. 复制 HTTPS URL(如 `https://your-machine.tail1234.ts.net` 或 `https://xxxx.ngrok-free.app`),在企业微信中设置回调地址:
242
- ```
243
- https://<your-public-host>/wechat-kf
244
- ```
245
- 4. 企业微信发送 GET 验证请求 — 插件自动解密 `echostr` 并响应
246
- 5. 从微信中通过客服链接发送测试消息,确认 Agent 正常回复
247
-
248
- ## 使用方式
249
-
250
- 配置完成并运行后,插件自动工作:
251
-
252
- 1. **用户**在微信中点击客服链接发起对话
253
- 2. **入站消息**通过 webhook 到达 → 插件解密、通过 `sync_msg` 同步消息、下载媒体附件,然后分发给 OpenClaw Agent
254
- 3. **Agent** 处理消息并生成回复
255
- 4. **出站回复**通过企业微信 `send_msg` API 发送,Markdown 自动转换为 Unicode 格式化纯文本
256
-
257
- ### 从 Agent 发送消息
258
-
259
- Agent 可以使用 `message` 工具发送消息:
260
-
261
- - **回复当前对话** — 省略 `target`,回复将发送给发消息的用户
262
- - **发送给特定用户** — 将 `target` 设为用户的 `external_userid`
263
- - **发送媒体** — 使用 `filePath` 或 `media` 附加图片、语音、视频或文件
264
-
265
- ### 支持的入站消息类型
266
-
267
- | 微信消息类型 | 处理方式 |
268
- | ------------ | ----------------------------------------- |
269
- | 文本 | 原样传递给 Agent |
270
- | 图片 | 下载保存为媒体附件,向 Agent 发送占位文本 |
271
- | 语音 | 下载为 AMR 格式,保存为媒体附件 |
272
- | 视频 | 下载为 MP4 格式,保存为媒体附件 |
273
- | 文件 | 下载保存为媒体附件 |
274
- | 位置 | 转换为文本:`[位置: 名称 地址]` |
275
- | 链接 | 转换为文本:`[链接: 标题 URL]`(含 desc、pic_url)|
276
- | 小程序 | 转换为文本,包含标题、appid 和 pagepath |
277
- | 视频号 | 转换为文本,包含类型、昵称、标题 |
278
- | 视频号商品 | 转换为文本,包含商品信息 |
279
- | 视频号订单 | 转换为文本,包含订单信息 |
280
- | 笔记 | 转换为文本,包含笔记内容 |
281
- | 名片 | 转换为文本,包含 userid |
282
- | 合并转发消息 | 解析并展开为可读文本 |
283
-
284
- ### 支持的出站消息类型
285
-
286
- 文本、图片、语音、视频、文件、链接、位置、小程序、菜单、名片、视频号文章和原始 JSON 消息(`[[wechat_raw:...]]`)。富消息类型通过 `[[wechat_*:...]]` 文本指令发送。所有来源的媒体(本地文件、HTTP URL、file:// URI)通过框架 loadWebMedia 加载后自动上传到微信临时素材存储。
287
-
288
- ## 架构
186
+ 开启后,新用户第一次发消息会收到配对码:
289
187
 
290
- ```
291
- 微信用户
292
- |
293
- v
294
- 企业微信服务器(腾讯)
295
- |
296
- |--- POST 回调 ---> webhook.ts ---> 验证签名 + 大小/方法守卫
297
- | (加密 XML) | 解密 AES-256-CBC
298
- | | 提取 OpenKfId + Token
299
- | v
300
- | bot.ts ---> DM 策略检查
301
- | | per-kfId 互斥锁 + msgid 去重
302
- | | sync_msg API(拉取消息)
303
- | | 基于游标的增量同步
304
- | | 处理事件(enter_session 等)
305
- | | 下载媒体附件
306
- | v
307
- | OpenClaw Agent(通过 runtime 分发)
308
- | |
309
- | +-----------+-----------+
310
- | v v
311
- | outbound.ts reply-dispatcher.ts
312
- | (框架驱动) (插件内部流式处理)
313
- | chunker 声明 markdown -> unicode
314
- | sendText / sendMedia 文本分块 + 延迟
315
- | | |
316
- | +-----------+-----------+
317
- | v
318
- | send-utils.ts
319
- | formatText, mediaKindToWechatType
320
- | detectMediaType, uploadAndSendMedia
321
- | resolveThumbMediaId
322
- | v
323
- +--- send_msg API <-- api.ts
324
- (JSON)
325
- ```
188
+ ![开启认证之后会收到需要配对的认证信息](docs/images/wechat-pairing-message.png)
326
189
 
327
- ### 核心模块
328
-
329
- | 模块 | 职责 |
330
- | --------------------- | ------------------------------------------------------------------------------------- |
331
- | `webhook.ts` | HTTP 处理器(框架网关)— GET 验证、POST 事件处理、大小/方法守卫 |
332
- | `crypto.ts` | AES-256-CBC 加密/解密、SHA-1 签名验证、PKCS#7 填充校验 |
333
- | `token.ts` | Access Token 缓存,哈希键存储,自动刷新 |
334
- | `api.ts` | 企业微信 API 客户端(sync_msg、send_msg、sendRawMessage、媒体上传/下载),Token 过期自动重试 |
335
- | `accounts.ts` | 动态客服账号发现、解析、启用/禁用/删除生命周期 |
336
- | `bot.ts` | 消息同步(互斥锁 + 去重)、DM 策略检查、事件处理、Agent 分发 |
337
- | `monitor.ts` | 共享上下文管理器(setSharedContext/getSharedContext/waitForSharedContext/clearSharedContext)|
338
- | `reply-dispatcher.ts` | 插件内部流式回复投递,包含分块、格式化、延迟 |
339
- | `outbound.ts` | 框架驱动的出站适配器,声明 chunker |
340
- | `send-utils.ts` | 共享出站工具(formatText、mediaKindToWechatType、detectMediaType、uploadAndSendMedia、resolveThumbMediaId) |
341
- | `wechat-kf-directives.ts` | `[[wechat_*:...]]` 指令解析器,用于 agent 回复中的富文本消息类型 |
342
- | `constants.ts` | 共享常量(WECHAT_TEXT_CHUNK_LIMIT、超时、错误码) |
343
- | `fs-utils.ts` | 原子文件操作(临时文件 + 重命名) |
344
- | `unicode-format.ts` | Markdown 转 Unicode 数学字母符号格式化 |
345
- | `channel.ts` | ChannelPlugin 接口,包含安全适配器(resolveDmPolicy、collectWarnings) |
346
- | `config-schema.ts` | wechat-kf 渠道配置的 JSON Schema 校验 |
347
- | `runtime.ts` | OpenClaw 运行时引用持有 |
348
-
349
- ### 状态持久化
350
-
351
- - **同步游标** — 按客服账号保存在 `~/.openclaw/state/wechat-kf/wechat-kf-cursor-{kfid}.txt`(原子写入)
352
- - **已发现的客服 ID** — 保存在 `~/.openclaw/state/wechat-kf/wechat-kf-kfids.json`(原子写入)
353
- - **Access Token** — 仅内存缓存,使用哈希键(重启后重新获取)
354
-
355
- ## 限制与已知问题
356
-
357
- - **开放访问特性** — 微信客服在微信生态中本质上是公开服务。任何获取到客服联系方式(链接或二维码)的人都可以向客服账号发送消息 — 这在微信平台层面无法阻止。插件的 `dmPolicy: "allowlist"` 模式可以限制 agent 只回复白名单内的用户(非白名单用户的消息会被静默丢弃,不会触发 agent 也不会收到回复),但无法阻止未知用户触达客服入口本身。请在生产环境部署前充分了解这一公开服务特性。
358
- - **48 小时回复窗口** — 微信仅允许在用户最后一条消息后 48 小时内回复。插件检测此限制(errcode 95026)并记录清晰警告。
359
- - **5 条消息限制** — 在用户发送下一条消息前,最多只能发送 5 条回复。插件检测此限制并相应记录日志。
360
- - **语音格式** — 入站语音消息为 AMR 格式;转录取决于 OpenClaw Agent 的媒体处理能力。
361
- - **仅临时素材** — 上传的媒体使用微信临时素材 API(3 天有效期)。未实现永久素材上传。
362
- - **单一 webhook 端点** — 所有客服账号共享同一个 webhook 路径。这是设计如此(企业微信为每个企业发送所有回调到同一个 URL)。
363
- - **不支持群聊** — 微信客服仅支持一对一会话。插件仅支持 `direct` 聊天类型。
364
- - **IP 白名单漂移** — 如果服务器公网 IP 变化,API 调用将静默失败。请监控 IP 或使用静态 IP。
365
-
366
- ## 开发
190
+ openclaw 所在电脑上运行以下命令批准:
367
191
 
368
192
  ```bash
369
- # 安装依赖
370
- pnpm install
193
+ openclaw pairing approve wechat-kf <配对码>
194
+ ```
371
195
 
372
- # 构建
373
- pnpm run build
196
+ ---
374
197
 
375
- # 类型检查
376
- pnpm run typecheck
198
+ ## 配置参考
377
199
 
378
- # 运行测试(17 个文件,约 600 个测试)
379
- pnpm test
200
+ | 字段 | 类型 | 必填 | 默认值 | 说明 |
201
+ | ---------------- | -------- | ------ | ------------ | -------------------------------------------------------------------------------------------------------------- |
202
+ | `enabled` | boolean | 否 | `false` | 是否启用该渠道 |
203
+ | `corpId` | string | **是** | — | 企业 ID |
204
+ | `appSecret` | string | **是** | — | 微信客服 Secret(从微信客服后台「开发配置」获取) |
205
+ | `token` | string | **是** | — | Webhook 回调 Token |
206
+ | `encodingAESKey` | string | **是** | — | 43 位 AES 加密密钥 |
207
+ | `webhookPath` | string | 否 | `/wechat-kf` | Webhook 回调 URL 路径 |
208
+ | `dmPolicy` | string | 否 | `"open"` | `open`(开放)/ `allowlist`(白名单)/ `pairing`(配对)/ `disabled`(禁用) |
209
+ | `allowFrom` | string[] | 否 | `[]` | 允许的 external_userid 列表(dmPolicy 为 `allowlist` 时使用) |
210
+ | `debounceMs` | number | 否 | `2000` | 消息防抖窗口(毫秒,0–10000):等待窗口内无新消息后再发给 Agent,适合用户分多条消息输入的场景;设为 `0` 可禁用 |
380
211
 
381
- # 监听模式
382
- pnpm run test:watch
212
+ ---
383
213
 
384
- # 代码检查(Biome)
385
- pnpm run lint
214
+ ## 有什么限制?
386
215
 
387
- # 代码检查 + 自动修复(Biome)
388
- pnpm run lint:fix
216
+ - **任何人都能发消息** 拿到客服链接的人都能发消息,这是微信平台层面的设计,无法阻止。可以用 `dmPolicy: "pairing"` 或 `"allowlist"` 让 Agent 只回复指定用户。
217
+ - **48 小时回复窗口** — 用户最后一条消息后 48 小时内才能回复。超时后需要用户再发一条消息才能继续。
218
+ - **5 条消息限制** — 在用户发下一条消息前,最多只能发 5 条回复。
219
+ - **语音格式** — 入站语音为 AMR 格式,是否能转录取决于你的 Agent 配置。
220
+ - **Tunnel 地址会变** — 使用 cloudflared 免费隧道时,每次重启地址会变。固定地址可以购买域名并解析到服务器,或使用付费 Tunnel(如 Cloudflare Zero Trust)、有静态 IP 的服务器。
389
221
 
390
- # 格式化(Biome)
391
- pnpm run format
222
+ ---
392
223
 
393
- # 综合 Biome 检查(代码检查 + 格式化)
394
- pnpm run check
395
- ```
224
+ ## 开发者文档
396
225
 
397
- ## 贡献
226
+ 架构设计、模块说明、开发命令和贡献流程,请参阅 [CONTRIBUTING.md](./CONTRIBUTING.md)。
398
227
 
399
- 1. Fork 本仓库
400
- 2. 创建功能分支(`git checkout -b feature/my-feature`)
401
- 3. 修改代码并添加测试
402
- 4. 运行 `pnpm run check && pnpm run typecheck && pnpm test` 验证
403
- 5. 提交 Pull Request
228
+ ---
404
229
 
405
230
  ## 许可证
406
231