@largezhou/ddingtalk 1.3.2 → 1.4.0-beta.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/README.en.md +361 -0
- package/README.md +281 -38
- package/package.json +5 -8
- package/src/accounts.ts +130 -45
- package/src/channel.ts +189 -89
- package/src/client.ts +75 -88
- package/src/ffmpeg.ts +206 -0
- package/src/monitor.ts +33 -8
- package/src/onboarding.ts +166 -56
- package/src/types.ts +38 -5
package/README.md
CHANGED
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
# @largezhou/ddingtalk
|
|
2
2
|
|
|
3
|
+
[English](README.en.md)
|
|
4
|
+
|
|
3
5
|
OpenClaw 钉钉(DingTalk)渠道插件,使用 Stream 模式接入企业机器人。
|
|
4
6
|
|
|
5
7
|
## 功能特点
|
|
6
8
|
|
|
7
9
|
- ✅ **Stream 模式**:无需公网 IP 和域名,开箱即用
|
|
10
|
+
- ✅ **多账号支持**:可同时接入多个钉钉机器人,分别配置凭证和权限
|
|
11
|
+
- ✅ **多 Agent 路由**:支持将不同账号、群聊、私聊绑定到不同的 Agent
|
|
8
12
|
- ✅ **私聊/群聊**:支持私聊,群聊(仅@机器人)
|
|
9
13
|
- ✅ **文本消息收发**:接收和发送文本消息
|
|
10
|
-
- ✅ **Markdown回复**:机器人回复 Markdown 格式
|
|
14
|
+
- ✅ **Markdown 回复**:机器人回复 Markdown 格式
|
|
11
15
|
- ✅ **图片消息收发**:接收用户发送的图片,支持发送本地/远程图片
|
|
12
16
|
- ✅ **语音、视频、文件、图文混排**:接收用户发送语音、视频、文件、图文混排消息
|
|
13
|
-
- ✅
|
|
17
|
+
- ✅ **回复文件**:支持回复文件,音频、视频等统一按文件发送
|
|
14
18
|
- ✅ **主动推送消息**:支持主动推送消息,可以配置提醒或定时任务
|
|
15
|
-
- ✅ **支持OpenClaw命令**:支持 /new、/compact 等 OpenClaw 官方命令
|
|
19
|
+
- ✅ **支持 OpenClaw 命令**:支持 /new、/compact 等 OpenClaw 官方命令
|
|
16
20
|
|
|
17
21
|
## 安装
|
|
18
22
|
|
|
@@ -20,53 +24,126 @@ OpenClaw 钉钉(DingTalk)渠道插件,使用 Stream 模式接入企业机
|
|
|
20
24
|
openclaw plugins install @largezhou/ddingtalk
|
|
21
25
|
```
|
|
22
26
|
|
|
23
|
-
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 快速开始
|
|
30
|
+
|
|
31
|
+
添加钉钉渠道有两种方式:
|
|
32
|
+
|
|
33
|
+
### 方式一:通过安装向导添加(推荐)
|
|
34
|
+
|
|
35
|
+
如果您刚安装完 OpenClaw,可以直接运行向导,根据提示添加钉钉:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
openclaw onboard
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
向导会引导您完成:
|
|
42
|
+
|
|
43
|
+
1. 创建钉钉应用机器人并获取凭证
|
|
44
|
+
2. 配置应用凭证
|
|
45
|
+
3. 启动网关
|
|
46
|
+
|
|
47
|
+
**完成配置后**,您可以使用以下命令检查网关状态:
|
|
48
|
+
|
|
49
|
+
- `openclaw gateway status` - 查看网关运行状态
|
|
50
|
+
- `openclaw logs --follow` - 查看实时日志
|
|
51
|
+
|
|
52
|
+
### 方式二:通过命令行添加
|
|
53
|
+
|
|
54
|
+
如果您已经完成了初始安装,可以用以下命令添加钉钉渠道:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
openclaw channels add
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
然后根据交互式提示选择 DingTalk,输入 AppKey (Client ID) 和 AppSecret (Client Secret) 即可。
|
|
61
|
+
|
|
62
|
+
**完成配置后**,您可以使用以下命令管理网关:
|
|
63
|
+
|
|
64
|
+
- `openclaw gateway status` - 查看网关运行状态
|
|
65
|
+
- `openclaw gateway restart` - 重启网关以应用新配置
|
|
66
|
+
- `openclaw logs --follow` - 查看实时日志
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## 第一步:创建钉钉应用
|
|
71
|
+
|
|
72
|
+
### 1. 打开钉钉开发者平台
|
|
73
|
+
|
|
74
|
+
访问 [钉钉开发者平台](https://open-dev.dingtalk.com/fe/app),使用钉钉账号登录,选择组织进入。
|
|
75
|
+
|
|
76
|
+
### 2. 创建应用
|
|
24
77
|
|
|
25
|
-
|
|
78
|
+
1. 点击右上角 **创建应用**
|
|
79
|
+
2. 填写应用名称和描述,上传图片(可选)
|
|
26
80
|
|
|
27
|
-
|
|
28
|
-
2. 创建应用
|
|
29
|
-
3. 记录 **AppKey** (ClientID) 和 **AppSecret** (ClientSecret)
|
|
81
|
+

|
|
30
82
|
|
|
31
|
-
###
|
|
83
|
+
### 3. 获取应用凭证
|
|
32
84
|
|
|
33
|
-
|
|
34
|
-
2. 选择 **机器人**
|
|
35
|
-
3. 填写机器人基本信息
|
|
36
|
-
4. **重要**: 消息接收模式选择 **Stream 模式**
|
|
37
|
-
5. 发布应用
|
|
85
|
+
在应用的 **凭证与基础信息** 页面,复制:
|
|
38
86
|
|
|
39
|
-
|
|
87
|
+
- **Client ID**(格式如 `dingxxxx`)
|
|
88
|
+
- **Client Secret**
|
|
89
|
+
|
|
90
|
+
❗ **重要**:请妥善保管 Client Secret,不要分享给他人。
|
|
91
|
+
|
|
92
|
+

|
|
93
|
+
|
|
94
|
+
### 4. 添加应用机器人
|
|
95
|
+
|
|
96
|
+
1. 在应用的 **添加应用能力** 页面,选择 **机器人**,点击添加
|
|
97
|
+
|
|
98
|
+

|
|
99
|
+
|
|
100
|
+
2. 输入机器人相关信息,**消息接收模式** 选择 **Stream 模式**,然后保存
|
|
101
|
+
|
|
102
|
+

|
|
103
|
+
|
|
104
|
+

|
|
105
|
+
|
|
106
|
+
### 5. 配置应用权限
|
|
40
107
|
|
|
41
108
|
在应用的权限管理中,确保开通以下权限:
|
|
42
109
|
|
|
43
110
|
- 企业内机器人发送消息权限
|
|
44
111
|
- 根据 downloadCode 获取机器人接收消息的下载链接(用于接收图片)
|
|
45
112
|
|
|
46
|
-
|
|
113
|
+
### 6. 发布机器人
|
|
114
|
+
|
|
115
|
+
创建机器人版本,填入版本号、描述、应用可用范围,点击保存,点击确认发布。
|
|
116
|
+
|
|
117
|
+

|
|
118
|
+
|
|
119
|
+

|
|
120
|
+
|
|
121
|
+
---
|
|
47
122
|
|
|
48
|
-
|
|
123
|
+
## 第二步:配置 OpenClaw
|
|
124
|
+
|
|
125
|
+
### 通过向导配置(推荐)
|
|
126
|
+
|
|
127
|
+
运行以下命令,根据提示选择 DingTalk,粘贴 AppKey (Client ID) 和 AppSecret (Client Secret):
|
|
49
128
|
|
|
50
129
|
```bash
|
|
51
130
|
openclaw channels add
|
|
52
131
|
```
|
|
53
132
|
|
|
54
|
-
|
|
133
|
+
### 通过配置文件配置
|
|
55
134
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
在 OpenClaw 配置文件 `~/.openclaw/openclaw.json` 中添加:
|
|
135
|
+
编辑 `~/.openclaw/openclaw.json`:
|
|
59
136
|
|
|
60
137
|
```json
|
|
61
138
|
{
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
139
|
+
"channels": {
|
|
140
|
+
"ddingtalk": {
|
|
141
|
+
"enabled": true,
|
|
142
|
+
"clientId": "your_app_key",
|
|
143
|
+
"clientSecret": "your_app_secret",
|
|
144
|
+
"allowFrom": ["*"]
|
|
145
|
+
}
|
|
146
|
+
}
|
|
70
147
|
}
|
|
71
148
|
```
|
|
72
149
|
|
|
@@ -84,31 +161,197 @@ openclaw channels add
|
|
|
84
161
|
}
|
|
85
162
|
```
|
|
86
163
|
|
|
87
|
-
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## 多账号配置
|
|
167
|
+
|
|
168
|
+
支持同时接入多个钉钉机器人,每个机器人对应一个独立的账号(account)。适用场景:
|
|
169
|
+
|
|
170
|
+
- 不同部门使用不同的机器人
|
|
171
|
+
- 同一个 OpenClaw 实例服务多个钉钉组织
|
|
172
|
+
- 不同机器人配置不同的权限策略
|
|
173
|
+
|
|
174
|
+
### 添加新账号
|
|
88
175
|
|
|
89
|
-
|
|
176
|
+
通过向导添加新账号,会交互式地提示输入账号 ID 和凭证:
|
|
90
177
|
|
|
91
178
|
```bash
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
179
|
+
openclaw channels add
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### 配置文件示例
|
|
95
183
|
|
|
96
|
-
|
|
97
|
-
|
|
184
|
+
编辑 `~/.openclaw/openclaw.json`:
|
|
185
|
+
|
|
186
|
+
```json
|
|
187
|
+
{
|
|
188
|
+
"channels": {
|
|
189
|
+
"ddingtalk": {
|
|
190
|
+
"enabled": true,
|
|
191
|
+
"accounts": {
|
|
192
|
+
"bot-hr": {
|
|
193
|
+
"name": "HR助手",
|
|
194
|
+
"clientId": "dingxxxxxxxx",
|
|
195
|
+
"clientSecret": "secret_1"
|
|
196
|
+
},
|
|
197
|
+
"bot-tech": {
|
|
198
|
+
"name": "技术支持",
|
|
199
|
+
"clientId": "dingyyyyyyyy",
|
|
200
|
+
"clientSecret": "secret_2"
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
"defaultAccount": "bot-hr"
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### 群组独立配置
|
|
210
|
+
|
|
211
|
+
可以为特定群聊设置独立的权限和行为:
|
|
212
|
+
|
|
213
|
+
```json
|
|
214
|
+
{
|
|
215
|
+
"accounts": {
|
|
216
|
+
"bot-hr": {
|
|
217
|
+
"enabled": true,
|
|
218
|
+
"clientId": "dingxxxxxxxx",
|
|
219
|
+
"clientSecret": "secret_1"
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### 单账号兼容
|
|
226
|
+
|
|
227
|
+
如果只有一个机器人,无需使用 `accounts`,直接在顶层配置即可(兼容旧版格式):
|
|
228
|
+
|
|
229
|
+
```json
|
|
230
|
+
{
|
|
231
|
+
"channels": {
|
|
232
|
+
"ddingtalk": {
|
|
233
|
+
"enabled": true,
|
|
234
|
+
"clientId": "your_app_key",
|
|
235
|
+
"clientSecret": "your_app_secret"
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## 多 Agent 路由
|
|
244
|
+
|
|
245
|
+
通过 OpenClaw 的路由绑定(bindings)机制,可以将不同的账号、群聊、私聊分配给不同的 Agent 处理。
|
|
246
|
+
|
|
247
|
+
> 更多关于多 Agent 的概念和用法,请参阅 [OpenClaw 官方文档 - 多 Agent](https://docs.openclaw.ai/zh-CN/concepts/multi-agent)。
|
|
248
|
+
|
|
249
|
+
### 按账号绑定 Agent
|
|
250
|
+
|
|
251
|
+
使用命令行将不同钉钉账号绑定到不同的 Agent:
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
# 将 bot-hr 账号绑定到 hr-agent
|
|
255
|
+
openclaw agents bind --agent hr-agent --bind ddingtalk:bot-hr
|
|
256
|
+
|
|
257
|
+
# 将 bot-tech 账号绑定到 tech-agent
|
|
258
|
+
openclaw agents bind --agent tech-agent --bind ddingtalk:bot-tech
|
|
259
|
+
|
|
260
|
+
# 将整个钉钉渠道(所有账号)绑定到默认 agent
|
|
261
|
+
openclaw agents bind --agent default-agent --bind ddingtalk
|
|
98
262
|
```
|
|
99
263
|
|
|
264
|
+
查看当前绑定:
|
|
265
|
+
|
|
266
|
+
```bash
|
|
267
|
+
openclaw agents bindings
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
解除绑定:
|
|
271
|
+
|
|
272
|
+
```bash
|
|
273
|
+
openclaw agents unbind --agent hr-agent --bind ddingtalk:bot-hr
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### 按群聊/私聊绑定 Agent
|
|
277
|
+
|
|
278
|
+
CLI 命令目前仅支持 `channel[:accountId]` 级别的绑定。如需将特定群聊或私聊绑定到不同 Agent,需要手动编辑 `~/.openclaw/openclaw.json` 的 `bindings` 配置:
|
|
279
|
+
|
|
280
|
+
```json
|
|
281
|
+
{
|
|
282
|
+
"agents": {
|
|
283
|
+
"list": [
|
|
284
|
+
{ "id": "hr-agent", "name": "HR助手" },
|
|
285
|
+
{ "id": "tech-agent", "name": "技术支持" },
|
|
286
|
+
{ "id": "general-agent", "name": "通用助手" }
|
|
287
|
+
]
|
|
288
|
+
},
|
|
289
|
+
"bindings": [
|
|
290
|
+
{
|
|
291
|
+
"agentId": "tech-agent",
|
|
292
|
+
"comment": "技术交流群走技术支持 Agent",
|
|
293
|
+
"match": {
|
|
294
|
+
"channel": "ddingtalk",
|
|
295
|
+
"peer": {
|
|
296
|
+
"kind": "group",
|
|
297
|
+
"id": "cidTechGroup001"
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
"agentId": "hr-agent",
|
|
303
|
+
"comment": "张三的私聊走HR助手",
|
|
304
|
+
"match": {
|
|
305
|
+
"channel": "ddingtalk",
|
|
306
|
+
"peer": {
|
|
307
|
+
"kind": "direct",
|
|
308
|
+
"id": "user_zhangsan_staffId"
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
"agentId": "general-agent",
|
|
314
|
+
"comment": "bot-hr 账号的其他消息走通用助手",
|
|
315
|
+
"match": {
|
|
316
|
+
"channel": "ddingtalk",
|
|
317
|
+
"accountId": "bot-hr"
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
]
|
|
321
|
+
}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
## 第三步:启动并测试
|
|
327
|
+
|
|
328
|
+
### 1. 启动网关
|
|
329
|
+
|
|
330
|
+
```bash
|
|
331
|
+
openclaw gateway --verbose
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### 2. 发送测试消息
|
|
335
|
+
|
|
336
|
+
在钉钉中找到您创建的机器人,即可正常对话。
|
|
337
|
+
|
|
338
|
+

|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
100
342
|
## 开发
|
|
101
343
|
|
|
102
344
|
```bash
|
|
103
345
|
# 安装依赖
|
|
104
|
-
|
|
346
|
+
pnpm install
|
|
105
347
|
|
|
106
348
|
# 打包
|
|
107
|
-
|
|
349
|
+
pnpm pack
|
|
108
350
|
```
|
|
109
351
|
|
|
110
352
|
## 参考文档
|
|
111
353
|
|
|
354
|
+
- [OpenClaw 多 Agent 文档](https://docs.openclaw.ai/zh-CN/concepts/multi-agent)
|
|
112
355
|
- [钉钉开放平台 - Stream 模式说明](https://opensource.dingtalk.com/developerpedia/docs/learn/stream/overview)
|
|
113
356
|
- [钉钉开放平台 - 机器人接收消息](https://open.dingtalk.com/document/orgapp/robot-receive-message)
|
|
114
357
|
- [钉钉开放平台 - 机器人发送消息](https://open.dingtalk.com/document/orgapp/robot-send-message)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@largezhou/ddingtalk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0-beta.0",
|
|
4
4
|
"description": "OpenClaw DingTalk (钉钉) channel plugin",
|
|
5
5
|
"main": "index.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -18,9 +18,7 @@
|
|
|
18
18
|
"openclaw.plugin.json",
|
|
19
19
|
"README.md"
|
|
20
20
|
],
|
|
21
|
-
"scripts": {
|
|
22
|
-
"demo": "npx tsx demo/demo.ts"
|
|
23
|
-
},
|
|
21
|
+
"scripts": {},
|
|
24
22
|
"keywords": [
|
|
25
23
|
"dingtalk",
|
|
26
24
|
"openclaw",
|
|
@@ -31,16 +29,15 @@
|
|
|
31
29
|
"author": "largezhou",
|
|
32
30
|
"license": "MIT",
|
|
33
31
|
"dependencies": {
|
|
34
|
-
"@alicloud/dingtalk": "^2.2.38",
|
|
35
|
-
"@alicloud/openapi-client": "^0.4.15",
|
|
36
|
-
"@alicloud/tea-util": "^1.4.11",
|
|
37
32
|
"dingtalk-stream": "^2.1.4"
|
|
38
33
|
},
|
|
39
34
|
"devDependencies": {
|
|
40
35
|
"@types/node": "^20.10.0",
|
|
36
|
+
"dotenv": "^17.3.1",
|
|
41
37
|
"openclaw": "*",
|
|
42
38
|
"tsx": "^4.6.0",
|
|
43
|
-
"typescript": "^5.3.0"
|
|
39
|
+
"typescript": "^5.3.0",
|
|
40
|
+
"zod": "^4.3.6"
|
|
44
41
|
},
|
|
45
42
|
"peerDependencies": {
|
|
46
43
|
"openclaw": "*",
|
package/src/accounts.ts
CHANGED
|
@@ -1,82 +1,167 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_ACCOUNT_ID,
|
|
3
|
+
normalizeAccountId,
|
|
4
|
+
type OpenClawConfig,
|
|
5
|
+
} from "openclaw/plugin-sdk";
|
|
6
|
+
import type {
|
|
7
|
+
DingTalkConfig,
|
|
8
|
+
DingTalkAccountConfig,
|
|
9
|
+
ResolvedDingTalkAccount,
|
|
10
|
+
} from "./types.js";
|
|
3
11
|
import { PLUGIN_ID } from "./constants.js";
|
|
4
12
|
|
|
13
|
+
// ======================= Account List Helpers =======================
|
|
14
|
+
|
|
15
|
+
export { normalizeAccountId };
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 列出所有钉钉账号 ID
|
|
19
|
+
*
|
|
20
|
+
* 方案 3 策略:顶层配置和 accounts 字典共存,不做迁移。
|
|
21
|
+
* - 顶层有 clientId → 视为 "default" 账号
|
|
22
|
+
* - accounts 字典中的 key → 各自独立的账号
|
|
23
|
+
* - 两者合并去重
|
|
24
|
+
*/
|
|
25
|
+
export function listDingTalkAccountIds(cfg: OpenClawConfig): string[] {
|
|
26
|
+
const dingtalkConfig = cfg.channels?.[PLUGIN_ID] as DingTalkConfig | undefined;
|
|
27
|
+
if (!dingtalkConfig) return [DEFAULT_ACCOUNT_ID];
|
|
28
|
+
|
|
29
|
+
const accountKeys = Object.keys(dingtalkConfig.accounts ?? {}).filter(Boolean);
|
|
30
|
+
|
|
31
|
+
// 顶层有凭据时,确保 "default" 在列表中
|
|
32
|
+
const hasTopLevel = Boolean(dingtalkConfig.clientId?.trim());
|
|
33
|
+
if (hasTopLevel && !accountKeys.includes(DEFAULT_ACCOUNT_ID)) {
|
|
34
|
+
accountKeys.push(DEFAULT_ACCOUNT_ID);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (accountKeys.length === 0) {
|
|
38
|
+
return [DEFAULT_ACCOUNT_ID];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return accountKeys.slice().sort((a, b) => a.localeCompare(b));
|
|
42
|
+
}
|
|
43
|
+
|
|
5
44
|
/**
|
|
6
|
-
*
|
|
45
|
+
* 解析默认账号 ID
|
|
46
|
+
*
|
|
47
|
+
* 优先使用 defaultAccount 配置,否则返回 "default"
|
|
7
48
|
*/
|
|
8
|
-
export function
|
|
9
|
-
|
|
49
|
+
export function resolveDefaultDingTalkAccountId(cfg: OpenClawConfig): string {
|
|
50
|
+
const dingtalkConfig = cfg.channels?.[PLUGIN_ID] as DingTalkConfig | undefined;
|
|
51
|
+
return dingtalkConfig?.defaultAccount?.trim() || DEFAULT_ACCOUNT_ID;
|
|
10
52
|
}
|
|
11
53
|
|
|
54
|
+
// ======================= Account Config Resolution =======================
|
|
55
|
+
|
|
12
56
|
/**
|
|
13
|
-
*
|
|
57
|
+
* 获取指定 accountId 的账户级配置(从 accounts 字典中查找)
|
|
14
58
|
*/
|
|
15
|
-
|
|
59
|
+
function resolveAccountConfig(
|
|
60
|
+
cfg: OpenClawConfig,
|
|
61
|
+
accountId: string,
|
|
62
|
+
): DingTalkAccountConfig | undefined {
|
|
16
63
|
const dingtalkConfig = cfg.channels?.[PLUGIN_ID] as DingTalkConfig | undefined;
|
|
17
|
-
|
|
18
|
-
|
|
64
|
+
const accounts = dingtalkConfig?.accounts;
|
|
65
|
+
if (!accounts || typeof accounts !== "object") {
|
|
66
|
+
return undefined;
|
|
19
67
|
}
|
|
20
|
-
|
|
68
|
+
const normalized = normalizeAccountId(accountId);
|
|
69
|
+
// 精确匹配或大小写不敏感匹配
|
|
70
|
+
const key = Object.keys(accounts).find(
|
|
71
|
+
(k) => normalizeAccountId(k) === normalized,
|
|
72
|
+
);
|
|
73
|
+
return key ? accounts[key] : undefined;
|
|
21
74
|
}
|
|
22
75
|
|
|
23
76
|
/**
|
|
24
|
-
*
|
|
77
|
+
* 合并顶层配置(作为默认值)和账户级配置
|
|
78
|
+
*
|
|
79
|
+
* 配置优先级:账户级 > 顶层
|
|
80
|
+
* 顶层的 accounts / defaultAccount 字段会被排除
|
|
25
81
|
*/
|
|
26
|
-
|
|
27
|
-
|
|
82
|
+
function mergeDingTalkAccountConfig(
|
|
83
|
+
cfg: OpenClawConfig,
|
|
84
|
+
accountId: string,
|
|
85
|
+
): DingTalkAccountConfig {
|
|
86
|
+
const dingtalkConfig = cfg.channels?.[PLUGIN_ID] as DingTalkConfig | undefined;
|
|
87
|
+
if (!dingtalkConfig) {
|
|
88
|
+
return {};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 顶层字段作为 base(排除 accounts 和 defaultAccount)
|
|
92
|
+
const {
|
|
93
|
+
accounts: _accounts,
|
|
94
|
+
defaultAccount: _defaultAccount,
|
|
95
|
+
groups: channelGroups,
|
|
96
|
+
...base
|
|
97
|
+
} = dingtalkConfig;
|
|
98
|
+
|
|
99
|
+
// 获取账户级配置
|
|
100
|
+
const account = resolveAccountConfig(cfg, accountId) ?? {};
|
|
101
|
+
|
|
102
|
+
// 多账户模式下,groups 不从顶层继承(每个账户应有自己的 groups)
|
|
103
|
+
const configuredAccountIds = Object.keys(dingtalkConfig.accounts ?? {});
|
|
104
|
+
const isMultiAccount = configuredAccountIds.length > 1;
|
|
105
|
+
const groups = account.groups ?? (isMultiAccount ? undefined : channelGroups);
|
|
106
|
+
|
|
107
|
+
return { ...base, ...account, groups };
|
|
28
108
|
}
|
|
29
109
|
|
|
110
|
+
// ======================= Account Resolution =======================
|
|
111
|
+
|
|
30
112
|
/**
|
|
31
|
-
*
|
|
113
|
+
* 解析钉钉账户配置
|
|
114
|
+
*
|
|
115
|
+
* 支持两种模式:
|
|
116
|
+
* 1. 单账户(旧版兼容):顶层 clientId/clientSecret → accountId = "default"
|
|
117
|
+
* 2. 多账户:accounts 字典 → 顶层字段作为默认值,账户级字段覆盖
|
|
32
118
|
*/
|
|
33
119
|
export function resolveDingTalkAccount(params: {
|
|
34
120
|
cfg: OpenClawConfig;
|
|
35
|
-
accountId?: string;
|
|
121
|
+
accountId?: string | null;
|
|
36
122
|
}): ResolvedDingTalkAccount {
|
|
37
|
-
const
|
|
38
|
-
const dingtalkConfig = cfg.channels?.[PLUGIN_ID] as DingTalkConfig | undefined;
|
|
123
|
+
const accountId = normalizeAccountId(params.accountId);
|
|
124
|
+
const dingtalkConfig = params.cfg.channels?.[PLUGIN_ID] as DingTalkConfig | undefined;
|
|
125
|
+
const baseEnabled = dingtalkConfig?.enabled !== false;
|
|
39
126
|
|
|
40
|
-
//
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
clientId: "",
|
|
45
|
-
clientSecret: "",
|
|
46
|
-
tokenSource: "none",
|
|
47
|
-
allowFrom: ["*"],
|
|
48
|
-
groupPolicy: "open",
|
|
49
|
-
groupAllowFrom: [],
|
|
50
|
-
groups: {},
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
if (!dingtalkConfig) {
|
|
54
|
-
return defaultResult;
|
|
55
|
-
}
|
|
127
|
+
// 合并顶层 + 账户级配置
|
|
128
|
+
const merged = mergeDingTalkAccountConfig(params.cfg, accountId);
|
|
129
|
+
const accountEnabled = merged.enabled !== false;
|
|
130
|
+
const enabled = baseEnabled && accountEnabled;
|
|
56
131
|
|
|
57
132
|
let clientId = "";
|
|
58
133
|
let clientSecret = "";
|
|
59
134
|
let tokenSource: ResolvedDingTalkAccount["tokenSource"] = "none";
|
|
60
135
|
|
|
61
|
-
if (
|
|
62
|
-
clientId =
|
|
136
|
+
if (merged.clientId?.trim()) {
|
|
137
|
+
clientId = merged.clientId.trim();
|
|
63
138
|
tokenSource = "config";
|
|
64
139
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
clientSecret = dingtalkConfig.clientSecret.trim();
|
|
140
|
+
if (merged.clientSecret?.trim()) {
|
|
141
|
+
clientSecret = merged.clientSecret.trim();
|
|
68
142
|
}
|
|
69
143
|
|
|
70
144
|
return {
|
|
71
|
-
accountId
|
|
72
|
-
name:
|
|
73
|
-
enabled
|
|
145
|
+
accountId,
|
|
146
|
+
name: merged.name?.trim() || undefined,
|
|
147
|
+
enabled,
|
|
74
148
|
clientId,
|
|
75
149
|
clientSecret,
|
|
76
150
|
tokenSource,
|
|
77
|
-
allowFrom:
|
|
78
|
-
groupPolicy:
|
|
79
|
-
groupAllowFrom:
|
|
80
|
-
groups:
|
|
151
|
+
allowFrom: merged.allowFrom ?? ["*"],
|
|
152
|
+
groupPolicy: merged.groupPolicy ?? "open",
|
|
153
|
+
groupAllowFrom: merged.groupAllowFrom ?? [],
|
|
154
|
+
groups: merged.groups ?? {},
|
|
81
155
|
};
|
|
82
156
|
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* 列出所有已启用的钉钉账户
|
|
160
|
+
*/
|
|
161
|
+
export function listEnabledDingTalkAccounts(
|
|
162
|
+
cfg: OpenClawConfig,
|
|
163
|
+
): ResolvedDingTalkAccount[] {
|
|
164
|
+
return listDingTalkAccountIds(cfg)
|
|
165
|
+
.map((accountId) => resolveDingTalkAccount({ cfg, accountId }))
|
|
166
|
+
.filter((account) => account.enabled);
|
|
167
|
+
}
|