@junjiezhang/openclaw-wecom-plugin 1.0.0
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/LICENSE +21 -0
- package/README.md +227 -0
- package/dist/accounts.d.ts +19 -0
- package/dist/accounts.d.ts.map +1 -0
- package/dist/accounts.js +52 -0
- package/dist/accounts.js.map +1 -0
- package/dist/bot.d.ts +30 -0
- package/dist/bot.d.ts.map +1 -0
- package/dist/bot.js +290 -0
- package/dist/bot.js.map +1 -0
- package/dist/channel.d.ts +4 -0
- package/dist/channel.d.ts.map +1 -0
- package/dist/channel.js +254 -0
- package/dist/channel.js.map +1 -0
- package/dist/client.d.ts +7 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +41 -0
- package/dist/client.js.map +1 -0
- package/dist/config-schema.d.ts +73 -0
- package/dist/config-schema.d.ts.map +1 -0
- package/dist/config-schema.js +79 -0
- package/dist/config-schema.js.map +1 -0
- package/dist/dedup.d.ts +3 -0
- package/dist/dedup.d.ts.map +1 -0
- package/dist/dedup.js +39 -0
- package/dist/dedup.js.map +1 -0
- package/dist/directory.d.ts +38 -0
- package/dist/directory.d.ts.map +1 -0
- package/dist/directory.js +66 -0
- package/dist/directory.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/media.d.ts +17 -0
- package/dist/media.d.ts.map +1 -0
- package/dist/media.js +64 -0
- package/dist/media.js.map +1 -0
- package/dist/monitor.d.ts +9 -0
- package/dist/monitor.d.ts.map +1 -0
- package/dist/monitor.js +233 -0
- package/dist/monitor.js.map +1 -0
- package/dist/outbound.d.ts +3 -0
- package/dist/outbound.d.ts.map +1 -0
- package/dist/outbound.js +24 -0
- package/dist/outbound.js.map +1 -0
- package/dist/policy.d.ts +29 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/policy.js +65 -0
- package/dist/policy.js.map +1 -0
- package/dist/probe.d.ts +6 -0
- package/dist/probe.d.ts.map +1 -0
- package/dist/probe.js +7 -0
- package/dist/probe.js.map +1 -0
- package/dist/reply-dispatcher.d.ts +16 -0
- package/dist/reply-dispatcher.d.ts.map +1 -0
- package/dist/reply-dispatcher.js +57 -0
- package/dist/reply-dispatcher.js.map +1 -0
- package/dist/runtime.d.ts +4 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +11 -0
- package/dist/runtime.js.map +1 -0
- package/dist/send.d.ts +18 -0
- package/dist/send.d.ts.map +1 -0
- package/dist/send.js +61 -0
- package/dist/send.js.map +1 -0
- package/dist/targets.d.ts +4 -0
- package/dist/targets.d.ts.map +1 -0
- package/dist/targets.js +16 -0
- package/dist/targets.js.map +1 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/openclaw.plugin.json +12 -0
- package/package.json +64 -0
- package/src/accounts.ts +81 -0
- package/src/bot.ts +410 -0
- package/src/channel.ts +278 -0
- package/src/client.ts +55 -0
- package/src/config-schema.ts +102 -0
- package/src/dedup.ts +60 -0
- package/src/directory.ts +150 -0
- package/src/index.ts +20 -0
- package/src/media.ts +105 -0
- package/src/monitor.ts +344 -0
- package/src/outbound.ts +26 -0
- package/src/policy.ts +108 -0
- package/src/probe.ts +13 -0
- package/src/reply-dispatcher.ts +78 -0
- package/src/runtime.ts +14 -0
- package/src/send.ts +91 -0
- package/src/targets.ts +21 -0
- package/src/types.d.ts +17 -0
- package/src/types.ts +3 -0
- package/tsconfig.json +32 -0
- package/types.d.ts +43 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 张俊杰
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# OpenClaw WeCom Plugin (企业微信插件)
|
|
2
|
+
|
|
3
|
+
[OpenClaw](https://openclaw.ai) 企业微信通道插件,让你可以通过企业微信与 AI 助手对话。
|
|
4
|
+
|
|
5
|
+
## 功能特性
|
|
6
|
+
|
|
7
|
+
- 支持企业微信消息收发
|
|
8
|
+
- 支持私聊和群聊
|
|
9
|
+
- 支持文本、图片、文件、语音、视频、位置、链接消息
|
|
10
|
+
- 访问控制策略(私聊:开放/配对/白名单;群聊:开放/白名单/禁用)
|
|
11
|
+
- 群聊 @提及要求
|
|
12
|
+
- 消息去重(处理企业微信重试)
|
|
13
|
+
- 访问令牌自动缓存和刷新
|
|
14
|
+
- 支持多账号配置
|
|
15
|
+
|
|
16
|
+
## 安装
|
|
17
|
+
|
|
18
|
+
### 方式一:从 npm 安装(推荐)
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
openclaw plugins install @junjiezhang/openclaw-wecom-plugin
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### 方式二:本地开发
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
git clone git@github.com:pk19876824/openclaw-wecom-plugin.git
|
|
28
|
+
cd openclaw-wecom-plugin
|
|
29
|
+
npm install
|
|
30
|
+
npm run build
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
在 OpenClaw 配置中添加插件路径:
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"plugins": {
|
|
38
|
+
"load": {
|
|
39
|
+
"paths": ["/path/to/openclaw-wecom"]
|
|
40
|
+
},
|
|
41
|
+
"entries": {
|
|
42
|
+
"wecom": { "enabled": true }
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## 配置
|
|
49
|
+
|
|
50
|
+
在 `~/.openclaw/openclaw.json` 中添加以下配置:
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{
|
|
54
|
+
"channels": {
|
|
55
|
+
"wecom": {
|
|
56
|
+
"enabled": true,
|
|
57
|
+
"corpId": "ww<your-corp-id>",
|
|
58
|
+
"agentId": "<your-agent-id>",
|
|
59
|
+
"secret": "<your-secret>",
|
|
60
|
+
"token": "<your-token>",
|
|
61
|
+
"encodingAESKey": "<your-encoding-aes-key>",
|
|
62
|
+
"webhookPort": 3000,
|
|
63
|
+
"webhookHost": "127.0.0.1",
|
|
64
|
+
"webhookPath": "/wecom/events",
|
|
65
|
+
"dmPolicy": "pairing",
|
|
66
|
+
"groupPolicy": "allowlist",
|
|
67
|
+
"requireMention": true,
|
|
68
|
+
"allowFrom": ["*"],
|
|
69
|
+
"groupAllowFrom": ["<group-chat-id>"],
|
|
70
|
+
"historyLimit": 50,
|
|
71
|
+
"dmHistoryLimit": 50,
|
|
72
|
+
"textChunkLimit": 2000,
|
|
73
|
+
"mediaMaxMb": 20
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 配置项说明
|
|
80
|
+
|
|
81
|
+
| 配置项 | 类型 | 默认值 | 说明 |
|
|
82
|
+
|--------|------|--------|------|
|
|
83
|
+
| `enabled` | boolean | `true` | 是否启用通道 |
|
|
84
|
+
| `corpId` | string | 必填 | 企业 ID |
|
|
85
|
+
| `agentId` | string | 必填 | 应用 AgentID |
|
|
86
|
+
| `secret` | string | 必填 | 应用 Secret |
|
|
87
|
+
| `token` | string | 必填 | Token(回调验证用) |
|
|
88
|
+
| `encodingAESKey` | string | 必填 | EncodingAESKey(消息加解密) |
|
|
89
|
+
| `webhookPort` | number | `3000` | Webhook 服务器端口 |
|
|
90
|
+
| `webhookHost` | string | `"127.0.0.1"` | Webhook 绑定地址 |
|
|
91
|
+
| `webhookPath` | string | `"/wecom/events"` | Webhook 路径 |
|
|
92
|
+
| `dmPolicy` | string | `"pairing"` | 私聊策略:`open` / `pairing` / `allowlist` |
|
|
93
|
+
| `groupPolicy` | string | `"allowlist"` | 群聊策略:`open` / `allowlist` / `disabled` |
|
|
94
|
+
| `requireMention` | boolean | `true` | 群聊是否需要 @机器人 |
|
|
95
|
+
| `allowFrom` | string[] | `[]` | 私聊白名单(用户 UserID,`"*"` 表示所有用户) |
|
|
96
|
+
| `groupAllowFrom` | string[] | `[]` | 群聊白名单(群聊 ID) |
|
|
97
|
+
| `historyLimit` | number | `50` | 会话历史消息数限制 |
|
|
98
|
+
| `dmHistoryLimit` | number | `50` | 私聊历史消息数限制 |
|
|
99
|
+
| `textChunkLimit` | number | `2000` | 文本分块大小限制 |
|
|
100
|
+
| `mediaMaxMb` | number | `20` | 媒体文件最大 MB |
|
|
101
|
+
|
|
102
|
+
### 访问控制策略
|
|
103
|
+
|
|
104
|
+
**私聊策略 (`dmPolicy`)**:
|
|
105
|
+
- `open`: 允许所有用户
|
|
106
|
+
- `pairing`: 需要用户先完成配对
|
|
107
|
+
- `allowlist`: 仅允许白名单用户
|
|
108
|
+
|
|
109
|
+
**群聊策略 (`groupPolicy`)**:
|
|
110
|
+
- `open`: 允许所有群聊
|
|
111
|
+
- `allowlist`: 仅允许白名单群聊
|
|
112
|
+
- `disabled`: 禁用群聊
|
|
113
|
+
|
|
114
|
+
## 使用
|
|
115
|
+
|
|
116
|
+
### 1. 启动 Gateway
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
openclaw gateway start
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### 2. 配置企业微信后台
|
|
123
|
+
|
|
124
|
+
1. 登录 [企业微信管理后台](https://work.weixin.qq.com/)
|
|
125
|
+
2. 进入「应用管理」→ 选择你的应用
|
|
126
|
+
3. 在「接收消息」中配置:
|
|
127
|
+
- **URL**: `http://<your-server>:3000/wecom/events`
|
|
128
|
+
- **Token**: 与配置文件中一致
|
|
129
|
+
- **EncodingAESKey**: 与配置文件中一致
|
|
130
|
+
4. 点击「保存」,系统会自动验证 URL
|
|
131
|
+
|
|
132
|
+
### 3. 测试消息
|
|
133
|
+
|
|
134
|
+
从企业微信发送消息给你的应用,AI 助手会自动回复。
|
|
135
|
+
|
|
136
|
+
### 4. 主动发送消息
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
# 发送给个人
|
|
140
|
+
openclaw message send --channel wecom --target <userid> --message "你好!"
|
|
141
|
+
|
|
142
|
+
# 发送给群聊
|
|
143
|
+
openclaw message send --channel wecom --target <groupid> --message "大家好!"
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## 安全说明
|
|
147
|
+
|
|
148
|
+
### Webhook 安全
|
|
149
|
+
|
|
150
|
+
- 所有请求都会验证签名(SHA1 HMAC)
|
|
151
|
+
- 请求体限制为 1 MB
|
|
152
|
+
- 解密后验证 CorpId 是否匹配
|
|
153
|
+
|
|
154
|
+
### 访问令牌
|
|
155
|
+
|
|
156
|
+
- 访问令牌仅存储在进程内存中
|
|
157
|
+
- 自动刷新(有效期 2 小时)
|
|
158
|
+
- 不会写入磁盘或日志
|
|
159
|
+
|
|
160
|
+
### 网络暴露
|
|
161
|
+
|
|
162
|
+
- 默认绑定 `127.0.0.1`(仅本地访问)
|
|
163
|
+
- 如需外网访问,设置 `webhookHost: "0.0.0.0"` 并配置防火墙
|
|
164
|
+
- 建议使用内网穿透工具(如 ngrok)进行开发测试
|
|
165
|
+
|
|
166
|
+
## 故障排查
|
|
167
|
+
|
|
168
|
+
### Webhook 验证失败
|
|
169
|
+
|
|
170
|
+
1. 检查 `token` 和 `encodingAESKey` 是否与后台配置完全一致
|
|
171
|
+
2. 确保 Gateway 已启动且 Webhook 服务器正常运行
|
|
172
|
+
3. 检查端口是否被占用:`lsof -i :3000`
|
|
173
|
+
4. 查看日志:`tail -f /tmp/openclaw/openclaw-$(date +%Y-%m-%d).log | grep wecom`
|
|
174
|
+
|
|
175
|
+
### 消息无法发送
|
|
176
|
+
|
|
177
|
+
1. 检查 `secret` 是否正确
|
|
178
|
+
2. 确认应用有「发消息」权限
|
|
179
|
+
3. 验证目标 UserID 或群聊 ID 是否正确
|
|
180
|
+
|
|
181
|
+
### 端口被占用
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
lsof -i :3000
|
|
185
|
+
# 或修改配置中的端口
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## 开发
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
git clone git@github.com:pk19876824/openclaw-wecom-plugin.git
|
|
192
|
+
cd openclaw-wecom-plugin
|
|
193
|
+
npm install
|
|
194
|
+
npm run build
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### 目录结构
|
|
198
|
+
|
|
199
|
+
```
|
|
200
|
+
openclaw-wecom/
|
|
201
|
+
├── src/
|
|
202
|
+
│ ├── index.ts # 插件入口
|
|
203
|
+
│ ├── channel.ts # 通道实现
|
|
204
|
+
│ ├── bot.ts # 消息处理
|
|
205
|
+
│ ├── client.ts # API 客户端
|
|
206
|
+
│ ├── config-schema.ts # 配置 Schema
|
|
207
|
+
│ ├── monitor.ts # Webhook 服务器
|
|
208
|
+
│ ├── send.ts # 发送消息
|
|
209
|
+
│ ├── policy.ts # 访问控制
|
|
210
|
+
│ ├── dedup.ts # 消息去重
|
|
211
|
+
│ └── ...
|
|
212
|
+
├── dist/ # 编译输出
|
|
213
|
+
├── package.json
|
|
214
|
+
├── openclaw.plugin.json # 插件清单
|
|
215
|
+
├── tsconfig.json
|
|
216
|
+
├── types.d.ts # 类型声明
|
|
217
|
+
└── README.md
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## 许可证
|
|
221
|
+
|
|
222
|
+
MIT License
|
|
223
|
+
|
|
224
|
+
## 致谢
|
|
225
|
+
|
|
226
|
+
- [OpenClaw](https://openclaw.ai) - AI 助手框架
|
|
227
|
+
- [企业微信 API 文档](https://developer.work.weixin.qq.com/document)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ClawdbotConfig } from "openclaw/plugin-sdk";
|
|
2
|
+
import type { ResolvedWeComAccount } from "./types.js";
|
|
3
|
+
export declare function listWeComAccountIds(cfg: ClawdbotConfig): string[];
|
|
4
|
+
export declare function resolveDefaultWeComAccountId(cfg: ClawdbotConfig): string;
|
|
5
|
+
export declare function resolveWeComAccount({ cfg, accountId, }: {
|
|
6
|
+
cfg: ClawdbotConfig;
|
|
7
|
+
accountId?: string;
|
|
8
|
+
}): ResolvedWeComAccount;
|
|
9
|
+
export declare function resolveWeComCredentials({ cfg, accountId, }: {
|
|
10
|
+
cfg: ClawdbotConfig;
|
|
11
|
+
accountId?: string;
|
|
12
|
+
}): {
|
|
13
|
+
corpId: string;
|
|
14
|
+
agentId: string;
|
|
15
|
+
secret: string;
|
|
16
|
+
token?: string;
|
|
17
|
+
encodingAESKey?: string;
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=accounts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"accounts.d.ts","sourceRoot":"","sources":["../src/accounts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,OAAO,KAAK,EAAe,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAEpE,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,cAAc,GAAG,MAAM,EAAE,CAIjE;AAED,wBAAgB,4BAA4B,CAAC,GAAG,EAAE,cAAc,GAAG,MAAM,CAExE;AAED,wBAAgB,mBAAmB,CAAC,EAClC,GAAG,EACH,SAAS,GACV,EAAE;IACD,GAAG,EAAE,cAAc,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GAAG,oBAAoB,CA+BvB;AAED,wBAAgB,uBAAuB,CAAC,EACtC,GAAG,EACH,SAAS,GACV,EAAE;IACD,GAAG,EAAE,cAAc,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GAAG;IACF,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAeA"}
|
package/dist/accounts.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk";
|
|
2
|
+
export function listWeComAccountIds(cfg) {
|
|
3
|
+
const wecomCfg = cfg.channels?.wecom;
|
|
4
|
+
if (!wecomCfg)
|
|
5
|
+
return [];
|
|
6
|
+
return [DEFAULT_ACCOUNT_ID];
|
|
7
|
+
}
|
|
8
|
+
export function resolveDefaultWeComAccountId(cfg) {
|
|
9
|
+
return DEFAULT_ACCOUNT_ID;
|
|
10
|
+
}
|
|
11
|
+
export function resolveWeComAccount({ cfg, accountId, }) {
|
|
12
|
+
const id = accountId ?? DEFAULT_ACCOUNT_ID;
|
|
13
|
+
const wecomCfg = cfg.channels?.wecom;
|
|
14
|
+
if (!wecomCfg) {
|
|
15
|
+
return {
|
|
16
|
+
accountId: id,
|
|
17
|
+
enabled: false,
|
|
18
|
+
configured: false,
|
|
19
|
+
name: "default",
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
const enabled = wecomCfg.enabled ?? false;
|
|
23
|
+
const configured = !!(wecomCfg.corpId &&
|
|
24
|
+
wecomCfg.agentId &&
|
|
25
|
+
wecomCfg.secret &&
|
|
26
|
+
wecomCfg.token &&
|
|
27
|
+
wecomCfg.encodingAESKey);
|
|
28
|
+
return {
|
|
29
|
+
accountId: id,
|
|
30
|
+
enabled,
|
|
31
|
+
configured,
|
|
32
|
+
name: "default",
|
|
33
|
+
corpId: wecomCfg.corpId,
|
|
34
|
+
agentId: wecomCfg.agentId,
|
|
35
|
+
config: wecomCfg,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export function resolveWeComCredentials({ cfg, accountId, }) {
|
|
39
|
+
const account = resolveWeComAccount({ cfg, accountId });
|
|
40
|
+
const wecomCfg = account.config;
|
|
41
|
+
if (!wecomCfg?.corpId || !wecomCfg?.agentId || !wecomCfg?.secret) {
|
|
42
|
+
throw new Error("WeCom credentials not configured");
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
corpId: wecomCfg.corpId,
|
|
46
|
+
agentId: wecomCfg.agentId,
|
|
47
|
+
secret: wecomCfg.secret,
|
|
48
|
+
token: wecomCfg.token,
|
|
49
|
+
encodingAESKey: wecomCfg.encodingAESKey,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=accounts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"accounts.js","sourceRoot":"","sources":["../src/accounts.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAGzD,MAAM,UAAU,mBAAmB,CAAC,GAAmB;IACrD,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,EAAE,KAAgC,CAAC;IAChE,IAAI,CAAC,QAAQ;QAAE,OAAO,EAAE,CAAC;IACzB,OAAO,CAAC,kBAAkB,CAAC,CAAC;AAC9B,CAAC;AAED,MAAM,UAAU,4BAA4B,CAAC,GAAmB;IAC9D,OAAO,kBAAkB,CAAC;AAC5B,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,EAClC,GAAG,EACH,SAAS,GAIV;IACC,MAAM,EAAE,GAAG,SAAS,IAAI,kBAAkB,CAAC;IAC3C,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,EAAE,KAAgC,CAAC;IAEhE,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO;YACL,SAAS,EAAE,EAAE;YACb,OAAO,EAAE,KAAK;YACd,UAAU,EAAE,KAAK;YACjB,IAAI,EAAE,SAAS;SAChB,CAAC;IACJ,CAAC;IAED,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,IAAI,KAAK,CAAC;IAC1C,MAAM,UAAU,GAAG,CAAC,CAAC,CACnB,QAAQ,CAAC,MAAM;QACf,QAAQ,CAAC,OAAO;QAChB,QAAQ,CAAC,MAAM;QACf,QAAQ,CAAC,KAAK;QACd,QAAQ,CAAC,cAAc,CACxB,CAAC;IAEF,OAAO;QACL,SAAS,EAAE,EAAE;QACb,OAAO;QACP,UAAU;QACV,IAAI,EAAE,SAAS;QACf,MAAM,EAAE,QAAQ,CAAC,MAAM;QACvB,OAAO,EAAE,QAAQ,CAAC,OAAO;QACzB,MAAM,EAAE,QAAQ;KACjB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,EACtC,GAAG,EACH,SAAS,GAIV;IAOC,MAAM,OAAO,GAAG,mBAAmB,CAAC,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC,CAAC;IACxD,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC;IAEhC,IAAI,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAC,QAAQ,EAAE,OAAO,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAC;QACjE,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;IACtD,CAAC;IAED,OAAO;QACL,MAAM,EAAE,QAAQ,CAAC,MAAM;QACvB,OAAO,EAAE,QAAQ,CAAC,OAAO;QACzB,MAAM,EAAE,QAAQ,CAAC,MAAM;QACvB,KAAK,EAAE,QAAQ,CAAC,KAAK;QACrB,cAAc,EAAE,QAAQ,CAAC,cAAc;KACxC,CAAC;AACJ,CAAC"}
|
package/dist/bot.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ClawdbotConfig, RuntimeEnv, HistoryEntry } from "openclaw/plugin-sdk";
|
|
2
|
+
export interface WeComMessageEvent {
|
|
3
|
+
ToUserName: string;
|
|
4
|
+
FromUserName: string;
|
|
5
|
+
CreateTime: number;
|
|
6
|
+
MsgType: string;
|
|
7
|
+
Content?: string;
|
|
8
|
+
MsgId: string;
|
|
9
|
+
AgentID: string;
|
|
10
|
+
PicUrl?: string;
|
|
11
|
+
MediaId?: string;
|
|
12
|
+
Title?: string;
|
|
13
|
+
Description?: string;
|
|
14
|
+
FileKey?: string;
|
|
15
|
+
Location_X?: string;
|
|
16
|
+
Location_Y?: string;
|
|
17
|
+
Scale?: string;
|
|
18
|
+
Label?: string;
|
|
19
|
+
Url?: string;
|
|
20
|
+
ChatId?: string;
|
|
21
|
+
ChatType?: string;
|
|
22
|
+
}
|
|
23
|
+
export declare function handleWeComMessage({ cfg, event, runtime, chatHistories, accountId, }: {
|
|
24
|
+
cfg: ClawdbotConfig;
|
|
25
|
+
event: WeComMessageEvent;
|
|
26
|
+
runtime?: RuntimeEnv;
|
|
27
|
+
chatHistories: Map<string, HistoryEntry[]>;
|
|
28
|
+
accountId?: string;
|
|
29
|
+
}): Promise<void>;
|
|
30
|
+
//# sourceMappingURL=bot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bot.d.ts","sourceRoot":"","sources":["../src/bot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAcpF,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAEhB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAKD,wBAAsB,kBAAkB,CAAC,EACvC,GAAG,EACH,KAAK,EACL,OAAO,EACP,aAAa,EACb,SAAS,GACV,EAAE;IACD,GAAG,EAAE,cAAc,CAAC;IACpB,KAAK,EAAE,iBAAiB,CAAC;IACzB,OAAO,CAAC,EAAE,UAAU,CAAC;IACrB,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,EAAE,CAAC,CAAC;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC,IAAI,CAAC,CAiWhB"}
|
package/dist/bot.js
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import { buildAgentMediaPayload, buildPendingHistoryContextFromMap, clearHistoryEntriesIfEnabled, createScopedPairingAccess, recordPendingHistoryEntryIfEnabled, } from "openclaw/plugin-sdk";
|
|
2
|
+
import { resolveWeComAccount } from "./accounts.js";
|
|
3
|
+
import { downloadImageWeCom, saveInboundImage } from "./media.js";
|
|
4
|
+
import { createWeComReplyDispatcher } from "./reply-dispatcher.js";
|
|
5
|
+
import { getWeComRuntime } from "./runtime.js";
|
|
6
|
+
import { sendMessageWeCom, sendGroupMessageWeCom } from "./send.js";
|
|
7
|
+
export async function handleWeComMessage({ cfg, event, runtime, chatHistories, accountId, }) {
|
|
8
|
+
const log = runtime?.log ?? console.log;
|
|
9
|
+
const error = runtime?.error ?? console.error;
|
|
10
|
+
const mediaList = [];
|
|
11
|
+
try {
|
|
12
|
+
const userId = event.FromUserName;
|
|
13
|
+
const messageId = event.MsgId;
|
|
14
|
+
const msgType = event.MsgType;
|
|
15
|
+
const chatId = event.ChatId;
|
|
16
|
+
const chatType = event.ChatType ?? (chatId ? "group" : "single");
|
|
17
|
+
const isGroupChat = chatType === "group" || !!chatId;
|
|
18
|
+
const wecomCfg = resolveWeComAccount({ cfg, accountId }).config;
|
|
19
|
+
const DEFAULT_HISTORY_LIMIT = 20;
|
|
20
|
+
const historyLimit = isGroupChat
|
|
21
|
+
? (wecomCfg?.historyLimit ?? DEFAULT_HISTORY_LIMIT)
|
|
22
|
+
: (wecomCfg?.dmHistoryLimit ?? DEFAULT_HISTORY_LIMIT);
|
|
23
|
+
try {
|
|
24
|
+
const { tryRecordMessagePersistent } = await import("./dedup.js");
|
|
25
|
+
const isNew = await tryRecordMessagePersistent(messageId, accountId ?? "default", log);
|
|
26
|
+
if (!isNew) {
|
|
27
|
+
log(`wecom[${accountId}]: duplicate message ${messageId}, skipping`);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
catch (dedupErr) {
|
|
32
|
+
error(`wecom[${accountId}]: dedup check failed: ${String(dedupErr)}`);
|
|
33
|
+
}
|
|
34
|
+
log(`wecom[${accountId}]: received ${msgType} message from ${userId} in ${isGroupChat ? "group" : "DM"}`);
|
|
35
|
+
const sessionKey = isGroupChat
|
|
36
|
+
? `wecom:${accountId}:group:${chatId}`
|
|
37
|
+
: `wecom:${accountId}:user:${userId}`;
|
|
38
|
+
let history = chatHistories.get(sessionKey);
|
|
39
|
+
if (!history) {
|
|
40
|
+
history = [];
|
|
41
|
+
chatHistories.set(sessionKey, history);
|
|
42
|
+
}
|
|
43
|
+
let messageText = "";
|
|
44
|
+
switch (msgType) {
|
|
45
|
+
case "text":
|
|
46
|
+
messageText = event.Content ?? "";
|
|
47
|
+
break;
|
|
48
|
+
case "image":
|
|
49
|
+
if (event.MediaId) {
|
|
50
|
+
try {
|
|
51
|
+
log(`wecom[${accountId}]: downloading image (mediaId=${event.MediaId})...`);
|
|
52
|
+
const { buffer, contentType, fileName } = await downloadImageWeCom({
|
|
53
|
+
cfg,
|
|
54
|
+
mediaId: event.MediaId,
|
|
55
|
+
accountId,
|
|
56
|
+
});
|
|
57
|
+
const filePath = await saveInboundImage({
|
|
58
|
+
buffer,
|
|
59
|
+
fileName: fileName || "image.jpg",
|
|
60
|
+
accountId: accountId || "default",
|
|
61
|
+
});
|
|
62
|
+
mediaList.push({ path: filePath, contentType: contentType || null });
|
|
63
|
+
messageText = "[图片]";
|
|
64
|
+
log(`wecom[${accountId}]: image downloaded: ${filePath}`);
|
|
65
|
+
}
|
|
66
|
+
catch (imgErr) {
|
|
67
|
+
log(`wecom[${accountId}]: image download failed: ${String(imgErr)}`);
|
|
68
|
+
messageText = "[图片]";
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
messageText = "[图片]";
|
|
73
|
+
}
|
|
74
|
+
break;
|
|
75
|
+
case "file":
|
|
76
|
+
messageText = `[文件: ${event.Title ?? "未知"}]`;
|
|
77
|
+
break;
|
|
78
|
+
case "location":
|
|
79
|
+
messageText = `[位置: ${event.Label ?? ""}] (${event.Location_X}, ${event.Location_Y})`;
|
|
80
|
+
break;
|
|
81
|
+
case "link":
|
|
82
|
+
messageText = `[链接: ${event.Title ?? ""}] ${event.Url ?? ""}`;
|
|
83
|
+
break;
|
|
84
|
+
case "voice":
|
|
85
|
+
case "video":
|
|
86
|
+
messageText = `[${msgType === "voice" ? "语音" : "视频"}]`;
|
|
87
|
+
log(`wecom[${accountId}]: ${msgType} message not fully supported yet`);
|
|
88
|
+
break;
|
|
89
|
+
default:
|
|
90
|
+
log(`wecom[${accountId}]: unsupported message type: ${msgType}`);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (isGroupChat) {
|
|
94
|
+
const account = resolveWeComAccount({ cfg, accountId });
|
|
95
|
+
const wecomCfg = account.config;
|
|
96
|
+
const { resolveWeComReplyPolicy, isWeComGroupAllowed } = await import("./policy.js");
|
|
97
|
+
const { resolveDefaultGroupPolicy, resolveAllowlistProviderRuntimeGroupPolicy } = await import("openclaw/plugin-sdk");
|
|
98
|
+
const defaultGroupPolicy = resolveDefaultGroupPolicy(cfg);
|
|
99
|
+
const { groupPolicy } = resolveAllowlistProviderRuntimeGroupPolicy({
|
|
100
|
+
providerConfigPresent: cfg.channels?.wecom !== undefined,
|
|
101
|
+
groupPolicy: wecomCfg?.groupPolicy,
|
|
102
|
+
defaultGroupPolicy,
|
|
103
|
+
});
|
|
104
|
+
if (!isWeComGroupAllowed({
|
|
105
|
+
groupPolicy,
|
|
106
|
+
allowFrom: wecomCfg?.groupAllowFrom ?? [],
|
|
107
|
+
senderId: chatId ?? "",
|
|
108
|
+
})) {
|
|
109
|
+
log(`wecom[${accountId}]: group ${chatId} not in allowlist, skipping`);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const { requireMention } = resolveWeComReplyPolicy({
|
|
113
|
+
isDirectMessage: false,
|
|
114
|
+
globalConfig: wecomCfg,
|
|
115
|
+
});
|
|
116
|
+
if (requireMention) {
|
|
117
|
+
log(`wecom[${accountId}]: group message without mention, skipping (requireMention=true)`);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (!isGroupChat) {
|
|
122
|
+
const account = resolveWeComAccount({ cfg, accountId });
|
|
123
|
+
const wecomCfg = account.config;
|
|
124
|
+
const dmPolicy = wecomCfg?.dmPolicy ?? "pairing";
|
|
125
|
+
if (dmPolicy !== "open") {
|
|
126
|
+
const { resolveWeComAllowlistMatch } = await import("./policy.js");
|
|
127
|
+
const core = getWeComRuntime();
|
|
128
|
+
const pairing = createScopedPairingAccess({
|
|
129
|
+
core,
|
|
130
|
+
channel: "wecom",
|
|
131
|
+
accountId: account.accountId,
|
|
132
|
+
});
|
|
133
|
+
const configAllowFrom = (wecomCfg?.allowFrom ?? []).map(String);
|
|
134
|
+
const storeAllowFrom = dmPolicy === "pairing" ? await pairing.readAllowFromStore().catch(() => []) : [];
|
|
135
|
+
const effectiveDmAllowFrom = [...configAllowFrom, ...storeAllowFrom];
|
|
136
|
+
const dmAllowed = resolveWeComAllowlistMatch({
|
|
137
|
+
allowFrom: effectiveDmAllowFrom,
|
|
138
|
+
senderId: userId,
|
|
139
|
+
}).allowed;
|
|
140
|
+
if (!dmAllowed) {
|
|
141
|
+
if (dmPolicy === "pairing") {
|
|
142
|
+
const { code, created } = await pairing.upsertPairingRequest({
|
|
143
|
+
id: userId,
|
|
144
|
+
meta: { name: userId },
|
|
145
|
+
});
|
|
146
|
+
if (created) {
|
|
147
|
+
log(`wecom[${accountId}]: pairing request from ${userId}`);
|
|
148
|
+
try {
|
|
149
|
+
await sendMessageWeCom({
|
|
150
|
+
cfg,
|
|
151
|
+
to: userId,
|
|
152
|
+
text: core.channel.pairing.buildPairingReply({
|
|
153
|
+
channel: "wecom",
|
|
154
|
+
idLine: `Your WeCom user ID: ${userId}`,
|
|
155
|
+
code,
|
|
156
|
+
}),
|
|
157
|
+
accountId,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
catch (pairErr) {
|
|
161
|
+
log(`wecom[${accountId}]: pairing reply failed for ${userId}: ${String(pairErr)}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
log(`wecom[${accountId}]: blocked unauthorized DM from ${userId} (dmPolicy=${dmPolicy})`);
|
|
167
|
+
}
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
let senderName = userId;
|
|
173
|
+
if (isGroupChat) {
|
|
174
|
+
try {
|
|
175
|
+
const { getUserInfoWeCom } = await import("./directory.js");
|
|
176
|
+
const userInfo = await getUserInfoWeCom({ cfg, userId, accountId });
|
|
177
|
+
senderName = userInfo.name || userId;
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
log(`wecom[${accountId}]: failed to get sender name: ${String(err)}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
const displayMessage = isGroupChat ? `${senderName}: ${messageText}` : messageText;
|
|
184
|
+
recordPendingHistoryEntryIfEnabled({
|
|
185
|
+
historyMap: chatHistories,
|
|
186
|
+
historyKey: sessionKey,
|
|
187
|
+
entry: {
|
|
188
|
+
sender: senderName,
|
|
189
|
+
body: messageText,
|
|
190
|
+
timestamp: event.CreateTime * 1000,
|
|
191
|
+
messageId: messageId,
|
|
192
|
+
},
|
|
193
|
+
limit: historyLimit,
|
|
194
|
+
});
|
|
195
|
+
const core = getWeComRuntime();
|
|
196
|
+
const historyContext = buildPendingHistoryContextFromMap({
|
|
197
|
+
historyMap: chatHistories,
|
|
198
|
+
historyKey: sessionKey,
|
|
199
|
+
limit: historyLimit,
|
|
200
|
+
currentMessage: displayMessage,
|
|
201
|
+
formatEntry: (entry) => core.channel.reply.formatAgentEnvelope({
|
|
202
|
+
channel: "WeCom",
|
|
203
|
+
from: isGroupChat ? `group:${chatId}` : userId,
|
|
204
|
+
timestamp: new Date(),
|
|
205
|
+
envelope: core.channel.reply.resolveEnvelopeFormatOptions(cfg),
|
|
206
|
+
body: `${entry.sender}: ${entry.body}`,
|
|
207
|
+
}),
|
|
208
|
+
});
|
|
209
|
+
const account = resolveWeComAccount({ cfg, accountId });
|
|
210
|
+
const route = core.channel.routing.resolveAgentRoute({
|
|
211
|
+
cfg,
|
|
212
|
+
channel: "wecom",
|
|
213
|
+
accountId: account.accountId,
|
|
214
|
+
peer: { kind: isGroupChat ? "group" : "direct", id: isGroupChat ? (chatId ?? "") : userId },
|
|
215
|
+
});
|
|
216
|
+
const { dispatcher, replyOptions, markDispatchIdle } = createWeComReplyDispatcher({
|
|
217
|
+
cfg,
|
|
218
|
+
agentId: route.agentId,
|
|
219
|
+
runtime: runtime,
|
|
220
|
+
userId,
|
|
221
|
+
chatId,
|
|
222
|
+
isGroupChat,
|
|
223
|
+
accountId: account.accountId,
|
|
224
|
+
});
|
|
225
|
+
log(`wecom[${accountId}]: dispatching to agent (session=${route.sessionKey})`);
|
|
226
|
+
const mediaPayload = buildAgentMediaPayload(mediaList);
|
|
227
|
+
const ctxPayload = core.channel.reply.finalizeInboundContext({
|
|
228
|
+
CommandBody: historyContext,
|
|
229
|
+
From: userId,
|
|
230
|
+
To: account.corpId ?? "",
|
|
231
|
+
SessionKey: route.sessionKey,
|
|
232
|
+
AccountId: route.accountId,
|
|
233
|
+
ChatType: isGroupChat ? "group" : "direct",
|
|
234
|
+
GroupSubject: isGroupChat ? chatId : undefined,
|
|
235
|
+
SenderName: senderName,
|
|
236
|
+
SenderId: userId,
|
|
237
|
+
Provider: "wecom",
|
|
238
|
+
Surface: "wecom",
|
|
239
|
+
MessageSid: messageId,
|
|
240
|
+
Timestamp: Date.now(),
|
|
241
|
+
CommandAuthorized: true,
|
|
242
|
+
OriginatingChannel: "wecom",
|
|
243
|
+
OriginatingTo: account.corpId ?? "",
|
|
244
|
+
...mediaPayload,
|
|
245
|
+
});
|
|
246
|
+
const { queuedFinal, counts } = await core.channel.reply.dispatchReplyFromConfig({
|
|
247
|
+
ctx: ctxPayload,
|
|
248
|
+
cfg,
|
|
249
|
+
dispatcher,
|
|
250
|
+
replyOptions,
|
|
251
|
+
});
|
|
252
|
+
markDispatchIdle();
|
|
253
|
+
clearHistoryEntriesIfEnabled({
|
|
254
|
+
historyMap: chatHistories,
|
|
255
|
+
historyKey: sessionKey,
|
|
256
|
+
limit: historyLimit,
|
|
257
|
+
});
|
|
258
|
+
log(`wecom[${accountId}]: dispatch complete (queuedFinal=${queuedFinal}, replies=${counts.final})`);
|
|
259
|
+
}
|
|
260
|
+
catch (err) {
|
|
261
|
+
error(`wecom[${accountId}]: error handling message: ${String(err)}`);
|
|
262
|
+
if (err instanceof Error && err.stack) {
|
|
263
|
+
error(`wecom[${accountId}]: stack trace: ${err.stack}`);
|
|
264
|
+
}
|
|
265
|
+
try {
|
|
266
|
+
const chatId = event.ChatId;
|
|
267
|
+
const isGroupChat = event.ChatType === "group" || !!chatId;
|
|
268
|
+
if (isGroupChat && chatId) {
|
|
269
|
+
await sendGroupMessageWeCom({
|
|
270
|
+
cfg,
|
|
271
|
+
chatId,
|
|
272
|
+
text: "抱歉,处理消息时出现错误。",
|
|
273
|
+
accountId,
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
await sendMessageWeCom({
|
|
278
|
+
cfg,
|
|
279
|
+
to: event.FromUserName,
|
|
280
|
+
text: "抱歉,处理消息时出现错误。",
|
|
281
|
+
accountId,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
catch (sendErr) {
|
|
286
|
+
error(`wecom[${accountId}]: failed to send error message: ${String(sendErr)}`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
//# sourceMappingURL=bot.js.map
|
package/dist/bot.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bot.js","sourceRoot":"","sources":["../src/bot.ts"],"names":[],"mappings":"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"}
|