@dingtalk-real-ai/dingtalk-connector 0.8.13-beta.0 → 0.8.13
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/CHANGELOG.md +22 -16
- package/README.en.md +28 -5
- package/README.md +28 -5
- package/docs/RELEASE_NOTES_V0.8.13.md +62 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -2
- package/src/core/message-handler.ts +4 -4
- package/src/reply-dispatcher.ts +2 -2
- package/src/services/media/file.ts +7 -2
- package/src/services/media.ts +19 -12
- package/src/services/messaging/card.ts +1 -1
- package/src/services/messaging.ts +7 -6
- package/src/utils/http-client.ts +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,35 +5,41 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
-
## [0.8.13
|
|
8
|
+
## [0.8.13] - 2026-04-08
|
|
9
9
|
|
|
10
10
|
### 修复 / Fixes
|
|
11
|
-
- 🐛
|
|
12
|
-
**Fix
|
|
11
|
+
- 🐛 **修复文件发送 mediaId 格式不一致导致静默失败** - `sendFileProactive` 在不同调用点传入 `cleanMediaId`(不带 `@`)和 `mediaId`(带 `@`),经实测钉钉 API 统一要求带 `@` 前缀。同时修复 catch 块吞掉异常导致外层误报成功
|
|
12
|
+
**Fix inconsistent mediaId format causing silent file send failure** - Unified to `@`-prefixed `mediaId`; fixed catch blocks swallowing errors
|
|
13
13
|
|
|
14
|
-
- 🐛
|
|
15
|
-
**Fix
|
|
14
|
+
- 🐛 **修复多账号场景下凭据未解析** - `sendText`/`sendMedia` 在多账号模式下 `clientId`/`clientSecret` 可能为 `SecretInput` 对象,现已用已解析值覆盖
|
|
15
|
+
**Fix unresolved credentials in multi-account mode** - Resolved values now override raw config
|
|
16
16
|
|
|
17
|
-
- 🐛
|
|
18
|
-
**Fix
|
|
17
|
+
- 🐛 **修复连接错误在 async 回调中无法传播** - 改为 `reject(new Error(...))` 确保 400/401 等错误正确传播
|
|
18
|
+
**Fix connection errors not propagating** - Changed `throw` to `reject()` inside async error handlers
|
|
19
19
|
|
|
20
|
-
- 🐛 **修复
|
|
21
|
-
**Fix
|
|
20
|
+
- 🐛 **修复 QPS 限流后立即重试** - 收到 403 QpsLimit 后同步更新 `lastUpdateTime`
|
|
21
|
+
**Fix QPS rate limit immediate retry** - `lastUpdateTime` is now synced when skipping
|
|
22
|
+
|
|
23
|
+
- 🐛 **修复 `resolveAllowFrom` 全局过滤误拦截群消息** - 返回空列表禁用框架层全局过滤,群消息由内部 `groupAllowFrom` 处理
|
|
24
|
+
**Fix resolveAllowFrom blocking group messages** - Returns `[]` to disable framework-level filtering
|
|
22
25
|
|
|
23
26
|
### 新增 / Added
|
|
24
|
-
- ✨ **消息路由支持 `group:`/`user:` 前缀** - `sendTextToDingTalk` 和 `sendMediaToDingTalk`
|
|
25
|
-
**Message routing supports `group:`/`user:` prefix targets** -
|
|
27
|
+
- ✨ **消息路由支持 `group:`/`user:` 前缀** - `sendTextToDingTalk` 和 `sendMediaToDingTalk` 新增前缀格式解析,兼容旧版裸 `cid` 前缀
|
|
28
|
+
**Message routing supports `group:`/`user:` prefix targets** - Backward compatible with bare `cid` prefix
|
|
26
29
|
|
|
27
30
|
### 改进 / Improvements
|
|
28
|
-
- ✅ **兼容 pdf-parse v1
|
|
29
|
-
**Support both pdf-parse v1 and v2 API** - Auto-detects export format
|
|
31
|
+
- ✅ **兼容 pdf-parse v1/v2 API** - 自动检测导出格式,v2.x 使用 class API,v1.x 使用函数 API
|
|
32
|
+
**Support both pdf-parse v1 and v2 API** - Auto-detects export format
|
|
33
|
+
|
|
34
|
+
- ✅ **`enableMediaUpload`/`systemPrompt` 移至共享配置** - 支持多账号独立配置
|
|
35
|
+
**Move `enableMediaUpload`/`systemPrompt` to shared config** - Per-account configurable
|
|
30
36
|
|
|
31
|
-
- ✅
|
|
32
|
-
**
|
|
37
|
+
- ✅ **新增出站路由测试** - 覆盖 `group:`/`user:` 前缀解析和消息路由场景
|
|
38
|
+
**Add outbound routing tests** - Covers prefix parsing and routing scenarios
|
|
33
39
|
|
|
34
40
|
### 文档 / Documentation
|
|
35
41
|
- 📝 **优化 README 安装验证说明** - 移除版本号硬编码,新增插件未加载时的警示提示
|
|
36
|
-
**Improve README plugin verification instructions** - Removed hardcoded version, added warning
|
|
42
|
+
**Improve README plugin verification instructions** - Removed hardcoded version, added warning
|
|
37
43
|
|
|
38
44
|
## [0.8.12] - 2026-04-01
|
|
39
45
|
|
package/README.en.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<div align="center">
|
|
2
|
-
<img alt="DingTalk" src="docs/images/dingtalk.svg" width="72" height="72" />
|
|
2
|
+
<img alt="DingTalk" src="https://raw.githubusercontent.com/DingTalk-Real-AI/dingtalk-openclaw-connector/main/docs/images/dingtalk.svg" width="72" height="72" />
|
|
3
3
|
<h1>Official DingTalk OpenClaw Connector</h1>
|
|
4
4
|
<p>Connect DingTalk bots to OpenClaw Gateway with AI Card streaming and session management</p>
|
|
5
5
|
|
|
@@ -135,13 +135,13 @@ You should see output similar to `✓ DingTalk Channel (vX.X.X) - loaded`.
|
|
|
135
135
|
1. Go to [DingTalk Open Platform](https://open-dev.dingtalk.com/)
|
|
136
136
|
2. Click **"Application Development"**
|
|
137
137
|
|
|
138
|
-

|
|
138
|
+

|
|
139
139
|
|
|
140
140
|
#### 2.2 Add Bot Capability
|
|
141
141
|
|
|
142
142
|
1. On the application details page, use the “one-click OpenClaw bot app” flow
|
|
143
143
|
|
|
144
|
-

|
|
144
|
+

|
|
145
145
|
|
|
146
146
|
#### 2.3 Get Credentials
|
|
147
147
|
|
|
@@ -149,9 +149,9 @@ You should see output similar to `✓ DingTalk Channel (vX.X.X) - loaded`.
|
|
|
149
149
|
2. Copy your **AppKey** (Client ID)
|
|
150
150
|
3. Copy your **AppSecret** (Client Secret)
|
|
151
151
|
|
|
152
|
-

|
|
152
|
+

|
|
153
153
|
|
|
154
|
-

|
|
154
|
+

|
|
155
155
|
|
|
156
156
|
> ⚠️ **Important**: Client ID and Client Secret are your bot’s unique credentials. Store them safely.
|
|
157
157
|
|
|
@@ -281,6 +281,29 @@ Both session routing/message policy options (including `pmpolicy` and `groupPoli
|
|
|
281
281
|
|
|
282
282
|
---
|
|
283
283
|
|
|
284
|
+
### Invalid Config: additional properties
|
|
285
|
+
|
|
286
|
+
**Symptoms**:
|
|
287
|
+
|
|
288
|
+
```
|
|
289
|
+
Problem:
|
|
290
|
+
- channels.dingtalk-connector: invalid config: must NOT have additional properties
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
**Cause**: Your config file contains deprecated or renamed fields that are no longer recognized.
|
|
294
|
+
|
|
295
|
+
**Solution**: Open `openclaw.config.yaml` and remove any unsupported fields under `channels.dingtalk-connector`. Known fields to remove:
|
|
296
|
+
|
|
297
|
+
| Old Field | Notes |
|
|
298
|
+
|-----------|-------|
|
|
299
|
+
| `gatewayPassword` | Deprecated legacy field |
|
|
300
|
+
| `gatewayToken` | Deprecated legacy field |
|
|
301
|
+
| `dmHistoryLimit` | Removed in v0.8.9 (never implemented) |
|
|
302
|
+
|
|
303
|
+
The error message will indicate the exact field name. Remove it and restart.
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
284
307
|
### HTTP 401 Error
|
|
285
308
|
|
|
286
309
|
**Symptoms**: Error message shows "401 Unauthorized"
|
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<div align="center">
|
|
2
|
-
<img alt="DingTalk" src="docs/images/dingtalk.svg" width="72" height="72" />
|
|
2
|
+
<img alt="DingTalk" src="https://raw.githubusercontent.com/DingTalk-Real-AI/dingtalk-openclaw-connector/main/docs/images/dingtalk.svg" width="72" height="72" />
|
|
3
3
|
<h1>钉钉 OpenClaw 官方连接器</h1>
|
|
4
4
|
<p>将钉钉机器人连接到 OpenClaw Gateway,支持 AI Card 流式响应和会话管理</p>
|
|
5
5
|
|
|
@@ -167,13 +167,13 @@ openclaw plugins list
|
|
|
167
167
|
1. 访问 [钉钉开放平台](https://open-dev.dingtalk.com/)
|
|
168
168
|
2. 点击 **"应用开发"**
|
|
169
169
|
|
|
170
|
-

|
|
170
|
+

|
|
171
171
|
|
|
172
172
|
#### 3.2 添加机器人能力
|
|
173
173
|
|
|
174
174
|
1. 在应用详情页,点击 一键创建OpenClaw机器人应用
|
|
175
175
|
|
|
176
|
-

|
|
176
|
+

|
|
177
177
|
|
|
178
178
|
#### 3.3 获取凭证
|
|
179
179
|
|
|
@@ -181,9 +181,9 @@ openclaw plugins list
|
|
|
181
181
|
2. 复制你的 **AppKey**(Client ID)
|
|
182
182
|
3. 复制你的 **AppSecret**(Client Secret)
|
|
183
183
|
|
|
184
|
-

|
|
184
|
+

|
|
185
185
|
|
|
186
|
-

|
|
186
|
+

|
|
187
187
|
|
|
188
188
|
> ⚠️ **重要**:Client ID和 Client Secret是机器人的唯一凭证。请合理保存。
|
|
189
189
|
|
|
@@ -314,6 +314,29 @@ openclaw logs --follow
|
|
|
314
314
|
|
|
315
315
|
---
|
|
316
316
|
|
|
317
|
+
### 配置字段不合法(additional properties)
|
|
318
|
+
|
|
319
|
+
**症状**:
|
|
320
|
+
|
|
321
|
+
```
|
|
322
|
+
Problem:
|
|
323
|
+
- channels.dingtalk-connector: invalid config: must NOT have additional properties
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
**原因**:配置文件中包含已废弃或已重命名的字段,连接器不再识别。
|
|
327
|
+
|
|
328
|
+
**解决方案**:打开 `openclaw.config.yaml`,删除 `channels.dingtalk-connector` 下不再支持的字段。已知需要删除的旧字段:
|
|
329
|
+
|
|
330
|
+
| 旧字段 | 说明 |
|
|
331
|
+
|--------|------|
|
|
332
|
+
| `gatewayPassword` | 早期版本字段,已废弃 |
|
|
333
|
+
| `gatewayToken` | 早期版本字段,已废弃 |
|
|
334
|
+
| `dmHistoryLimit` | v0.8.9 移除(未实现) |
|
|
335
|
+
|
|
336
|
+
错误信息会指出具体的字段名,删除后重启即可。
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
317
340
|
### HTTP 401 错误
|
|
318
341
|
|
|
319
342
|
**症状**:错误信息显示 "401 Unauthorized"
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Release Notes - v0.8.13
|
|
2
|
+
|
|
3
|
+
## 🎉 新版本亮点 / Highlights
|
|
4
|
+
|
|
5
|
+
本次更新聚焦**媒体发送可靠性**和**多账号稳定性**:修复了文件发送时 mediaId 格式不一致导致静默失败的问题,修复了多账号凭据解析错误、连接错误无法传播、QPS 限流重试等多个关键 Bug,同时新增 `group:`/`user:` 前缀路由支持。
|
|
6
|
+
|
|
7
|
+
This release focuses on **media sending reliability** and **multi-account stability**: fixes silent file sending failures caused by inconsistent mediaId format, resolves multi-account credential resolution errors, connection error propagation, QPS throttle retry issues, and adds `group:`/`user:` prefix routing support.
|
|
8
|
+
|
|
9
|
+
## 🐛 修复 / Fixes
|
|
10
|
+
|
|
11
|
+
- **修复文件发送时 mediaId 格式不一致导致静默失败 / Fix inconsistent mediaId format causing silent file send failure**
|
|
12
|
+
`sendFileProactive` 在不同调用点传入了 `cleanMediaId`(不带 `@`)和 `mediaId`(带 `@`),经实测钉钉 API 统一要求带 `@` 前缀的 `mediaId`。同时修复了 `sendFileMessage`/`sendFileProactive` 的 catch 块吞掉异常导致外层误报成功的问题。
|
|
13
|
+
`sendFileProactive` was called with `cleanMediaId` (without `@`) in some places and `mediaId` (with `@`) in others. Testing confirmed DingTalk API requires the `@`-prefixed `mediaId`. Also fixed catch blocks swallowing errors, causing false success reports.
|
|
14
|
+
|
|
15
|
+
- **修复多账号场景下凭据未解析 / Fix unresolved credentials in multi-account mode**
|
|
16
|
+
`sendText`/`sendMedia` 在多账号模式下 `clientId`/`clientSecret` 可能为 `SecretInput` 对象,导致 API 请求携带无效凭据。现已用 `resolveDingtalkAccount` 返回的已解析值覆盖。
|
|
17
|
+
In multi-account mode, credentials could be `SecretInput` objects; now resolved values are used.
|
|
18
|
+
|
|
19
|
+
- **修复连接错误在 async 回调中无法传播 / Fix connection errors not propagating**
|
|
20
|
+
`connection.ts` 错误处理使用 `throw` 抛出,但在 async 回调内无法被外层 Promise 捕获。改为 `reject(new Error(...))` 确保 400/401 等错误正确传播。
|
|
21
|
+
Changed `throw` to `reject()` inside async error handlers for proper error propagation.
|
|
22
|
+
|
|
23
|
+
- **修复 QPS 限流后立即重试 / Fix QPS rate limit immediate retry**
|
|
24
|
+
收到 403 QpsLimit 后未更新 `lastUpdateTime`,导致节流检查立即放行。现已同步更新。
|
|
25
|
+
`lastUpdateTime` is now synced when skipping a QPS-limited update.
|
|
26
|
+
|
|
27
|
+
- **修复 `resolveAllowFrom` 全局过滤误拦截群消息 / Fix resolveAllowFrom blocking group messages**
|
|
28
|
+
`allowFrom` 仅用于私聊白名单,群消息由 `groupAllowFrom` 在内部处理。将 `resolveAllowFrom` 改为返回空列表,禁用框架层全局过滤。
|
|
29
|
+
Returns `[]` to disable framework-level filtering; internal policy checks handle DM and group separately.
|
|
30
|
+
|
|
31
|
+
## ✨ 功能改进 / Improvements
|
|
32
|
+
|
|
33
|
+
- **消息路由支持 `group:`/`user:` 前缀 / Message routing supports `group:`/`user:` prefix targets**
|
|
34
|
+
`sendTextToDingTalk` 和 `sendMediaToDingTalk` 新增 `group:<id>` 和 `user:<id>` 格式解析,兼容旧版裸 `cid` 前缀。
|
|
35
|
+
Now supports `group:<id>` and `user:<id>` prefixed targets, backward compatible with bare `cid` prefix.
|
|
36
|
+
|
|
37
|
+
- **兼容 pdf-parse v1/v2 API、共享配置字段扩展 / pdf-parse v1/v2 compatibility & shared config fields**
|
|
38
|
+
`parsePdfFile` 自动检测 pdf-parse 导出格式;`enableMediaUpload`/`systemPrompt` 移至共享配置,支持多账号独立配置。
|
|
39
|
+
Auto-detects pdf-parse export format; `enableMediaUpload`/`systemPrompt` now configurable per-account.
|
|
40
|
+
|
|
41
|
+
- **新增出站路由测试 / Add outbound routing tests**
|
|
42
|
+
新增 `outbound-routing.test.ts`,覆盖 `group:`/`user:` 前缀解析和消息路由场景。
|
|
43
|
+
Added `outbound-routing.test.ts` covering prefix parsing and message routing scenarios.
|
|
44
|
+
|
|
45
|
+
## 📥 安装升级 / Installation & Upgrade
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
openclaw plugins install @dingtalk-real-ai/dingtalk-connector
|
|
49
|
+
openclaw plugins update dingtalk-connector
|
|
50
|
+
openclaw plugins install https://github.com/DingTalk-Real-AI/dingtalk-openclaw-connector.git
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## 🔗 相关链接 / Related Links
|
|
54
|
+
|
|
55
|
+
- [完整变更日志 / Full Changelog](https://github.com/DingTalk-Real-AI/dingtalk-openclaw-connector/blob/main/CHANGELOG.md)
|
|
56
|
+
- [使用文档 / Documentation](https://github.com/DingTalk-Real-AI/dingtalk-openclaw-connector/blob/main/README.md)
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
**发布日期 / Release Date**:2026-04-08
|
|
61
|
+
**版本号 / Version**:v0.8.13
|
|
62
|
+
**兼容性 / Compatibility**:OpenClaw Gateway 0.4.0+
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dingtalk-real-ai/dingtalk-connector",
|
|
3
|
-
"version": "0.8.13
|
|
3
|
+
"version": "0.8.13",
|
|
4
4
|
"description": "DingTalk (钉钉) channel connector — Stream mode with AI Card streaming",
|
|
5
5
|
"main": "index.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"access": "public"
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"axios": "1.
|
|
58
|
+
"axios": "1.14.0",
|
|
59
59
|
"dingtalk-stream": "2.1.4",
|
|
60
60
|
"form-data": "^4.0.0",
|
|
61
61
|
"zod": "^4.3.6"
|
|
@@ -49,7 +49,7 @@ import {
|
|
|
49
49
|
processLocalImages,
|
|
50
50
|
processVideoMarkers,
|
|
51
51
|
processAudioMarkers,
|
|
52
|
-
|
|
52
|
+
uploadAndReplaceFileMarkers
|
|
53
53
|
} from "../services/media/index.ts";
|
|
54
54
|
import { sendProactive, type AICardTarget } from "../services/messaging/index.ts";
|
|
55
55
|
import { createAICardForTarget, streamAICard, type AICardInstance } from "../services/messaging/card.ts";
|
|
@@ -701,7 +701,7 @@ export async function downloadMediaByCode(
|
|
|
701
701
|
|
|
702
702
|
const resp = await dingtalkHttp.post(
|
|
703
703
|
`${DINGTALK_API}/v1.0/robot/messageFiles/download`,
|
|
704
|
-
{ downloadCode, robotCode: config.clientId },
|
|
704
|
+
{ downloadCode, robotCode: String(config.clientId) },
|
|
705
705
|
{
|
|
706
706
|
headers: { 'x-acs-dingtalk-access-token': token, 'Content-Type': 'application/json' },
|
|
707
707
|
timeout: 30_000,
|
|
@@ -733,7 +733,7 @@ export async function getFileDownloadUrl(
|
|
|
733
733
|
|
|
734
734
|
const resp = await dingtalkHttp.post(
|
|
735
735
|
`${DINGTALK_API}/v1.0/robot/messageFiles/download`,
|
|
736
|
-
{ downloadCode, robotCode: config.clientId },
|
|
736
|
+
{ downloadCode, robotCode: String(config.clientId) },
|
|
737
737
|
{
|
|
738
738
|
headers: { 'x-acs-dingtalk-access-token': token, 'Content-Type': 'application/json' },
|
|
739
739
|
timeout: 30_000,
|
|
@@ -1513,7 +1513,7 @@ export async function handleDingTalkMessageInternal(params: HandleMessageParams)
|
|
|
1513
1513
|
true, // ✅ 使用主动 API 模式
|
|
1514
1514
|
mediaTarget
|
|
1515
1515
|
);
|
|
1516
|
-
finalText = await
|
|
1516
|
+
finalText = await uploadAndReplaceFileMarkers(
|
|
1517
1517
|
finalText,
|
|
1518
1518
|
'',
|
|
1519
1519
|
config,
|
package/src/reply-dispatcher.ts
CHANGED
|
@@ -43,7 +43,7 @@ import {
|
|
|
43
43
|
processLocalImages,
|
|
44
44
|
processVideoMarkers,
|
|
45
45
|
processAudioMarkers,
|
|
46
|
-
|
|
46
|
+
uploadAndReplaceFileMarkers,
|
|
47
47
|
} from "./services/media/index.ts";
|
|
48
48
|
|
|
49
49
|
|
|
@@ -314,7 +314,7 @@ export function createDingtalkReplyDispatcher(params: CreateDingtalkReplyDispatc
|
|
|
314
314
|
true, // ✅ 使用主动 API 模式
|
|
315
315
|
target
|
|
316
316
|
);
|
|
317
|
-
finalText = await
|
|
317
|
+
finalText = await uploadAndReplaceFileMarkers(
|
|
318
318
|
finalText,
|
|
319
319
|
'',
|
|
320
320
|
account.config as DingtalkConfig,
|
|
@@ -29,9 +29,14 @@ export async function parseDocumentFile(filePath: string, log?: any): Promise<st
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
|
-
*
|
|
32
|
+
* 提取文件标记,上传文件到钉钉,并用文本替换标记。
|
|
33
|
+
*
|
|
34
|
+
* 注意:此函数只做「上传 + 文本替换」,不会发送独立的文件消息。
|
|
35
|
+
* 如果需要上传后再发送独立文件消息,请使用 media.ts 中的 processFileMarkers。
|
|
36
|
+
*
|
|
37
|
+
* 调用方:reply-dispatcher.ts、message-handler.ts(通过 media/index.ts 导入)
|
|
33
38
|
*/
|
|
34
|
-
export async function
|
|
39
|
+
export async function uploadAndReplaceFileMarkers(
|
|
35
40
|
content: string,
|
|
36
41
|
sessionWebhook: string,
|
|
37
42
|
config: DingtalkConfig,
|
package/src/services/media.ts
CHANGED
|
@@ -607,7 +607,12 @@ export interface FileInfo {
|
|
|
607
607
|
}
|
|
608
608
|
|
|
609
609
|
/**
|
|
610
|
-
*
|
|
610
|
+
* 提取文件标记,上传文件到钉钉,并发送独立的文件消息(webhook 或 proactive API)。
|
|
611
|
+
*
|
|
612
|
+
* 注意:此函数既做「上传」也做「发送」,是完整版的文件处理流程。
|
|
613
|
+
* 与 media/file.ts 中的 uploadAndReplaceFileMarkers 不同,后者只做上传+文本替换。
|
|
614
|
+
*
|
|
615
|
+
* 调用方:messaging.ts(直接 import media.ts)
|
|
611
616
|
*/
|
|
612
617
|
export async function processFileMarkers(
|
|
613
618
|
content: string,
|
|
@@ -672,11 +677,11 @@ export async function processFileMarkers(
|
|
|
672
677
|
continue;
|
|
673
678
|
}
|
|
674
679
|
|
|
675
|
-
//
|
|
680
|
+
// 发送文件消息(钉钉 API 统一要求带 @ 前缀的 mediaId)
|
|
676
681
|
if (useProactiveApi && target) {
|
|
677
|
-
await sendFileProactive(config, target, fileInfo, uploadResult.
|
|
682
|
+
await sendFileProactive(config, target, fileInfo, uploadResult.mediaId, log);
|
|
678
683
|
} else {
|
|
679
|
-
await sendFileMessage(config, sessionWebhook, fileInfo, uploadResult.
|
|
684
|
+
await sendFileMessage(config, sessionWebhook, fileInfo, uploadResult.mediaId, log);
|
|
680
685
|
}
|
|
681
686
|
statusMessages.push(`✅ 文件已发送: ${fileName}`);
|
|
682
687
|
log?.info?.(`${logPrefix} 文件处理完成: ${fileName}`);
|
|
@@ -771,7 +776,7 @@ export async function sendVideoProactive(
|
|
|
771
776
|
};
|
|
772
777
|
|
|
773
778
|
const body: any = {
|
|
774
|
-
robotCode: config.clientId,
|
|
779
|
+
robotCode: String(config.clientId),
|
|
775
780
|
msgKey: 'sampleVideo',
|
|
776
781
|
msgParam: JSON.stringify(msgParam),
|
|
777
782
|
};
|
|
@@ -873,7 +878,7 @@ export async function sendAudioProactive(
|
|
|
873
878
|
};
|
|
874
879
|
|
|
875
880
|
const body: any = {
|
|
876
|
-
robotCode: config.clientId,
|
|
881
|
+
robotCode: String(config.clientId),
|
|
877
882
|
msgKey: 'sampleAudio',
|
|
878
883
|
msgParam: JSON.stringify(msgParam),
|
|
879
884
|
};
|
|
@@ -942,6 +947,7 @@ async function sendFileMessage(
|
|
|
942
947
|
}
|
|
943
948
|
} catch (err: any) {
|
|
944
949
|
log?.error?.(`发送文件消息异常: ${fileInfo.fileName}, 错误: ${err.message}`);
|
|
950
|
+
throw err;
|
|
945
951
|
}
|
|
946
952
|
}
|
|
947
953
|
|
|
@@ -960,14 +966,16 @@ export async function sendFileProactive(
|
|
|
960
966
|
const { DINGTALK_API } = await import('../utils/index.ts');
|
|
961
967
|
|
|
962
968
|
// 钉钉普通消息 API 的文件消息格式
|
|
969
|
+
const resolvedFileName = fileInfo.fileName || path.basename(fileInfo.path);
|
|
970
|
+
const resolvedFileType = fileInfo.fileType || resolvedFileName.split('.').pop() || 'file';
|
|
963
971
|
const msgParam = {
|
|
964
972
|
mediaId: mediaId,
|
|
965
|
-
fileName:
|
|
966
|
-
fileType:
|
|
973
|
+
fileName: resolvedFileName,
|
|
974
|
+
fileType: resolvedFileType,
|
|
967
975
|
};
|
|
968
976
|
|
|
969
977
|
const body: any = {
|
|
970
|
-
robotCode: config.clientId,
|
|
978
|
+
robotCode: String(config.clientId),
|
|
971
979
|
msgKey: 'sampleFile',
|
|
972
980
|
msgParam: JSON.stringify(msgParam),
|
|
973
981
|
};
|
|
@@ -994,6 +1002,7 @@ export async function sendFileProactive(
|
|
|
994
1002
|
}
|
|
995
1003
|
} catch (err: any) {
|
|
996
1004
|
log?.error?.(`File[Proactive] 发送文件消息失败: ${fileInfo.fileName}, 错误: ${err.message}`);
|
|
1005
|
+
throw err;
|
|
997
1006
|
}
|
|
998
1007
|
}
|
|
999
1008
|
|
|
@@ -1109,9 +1118,7 @@ export async function processRawMediaPaths(
|
|
|
1109
1118
|
};
|
|
1110
1119
|
|
|
1111
1120
|
if (target) {
|
|
1112
|
-
|
|
1113
|
-
// 文件消息使用媒体文件ID(cleanMediaId)通过主动API发送
|
|
1114
|
-
await sendFileProactive(config, target, fileInfo, uploadResult.cleanMediaId, log);
|
|
1121
|
+
await sendFileProactive(config, target, fileInfo, uploadResult.mediaId, log);
|
|
1115
1122
|
}
|
|
1116
1123
|
statusMessages.push(`✅ 文件已发送: ${fileName}`);
|
|
1117
1124
|
}
|
|
@@ -303,7 +303,7 @@ export async function finishAICard(
|
|
|
303
303
|
`[DingTalk][AICard] 开始 finish,最终内容长度=${fixedContent.length}`,
|
|
304
304
|
);
|
|
305
305
|
|
|
306
|
-
await streamAICard(card, fixedContent, true, log);
|
|
306
|
+
await streamAICard(card, fixedContent, true, config, log);
|
|
307
307
|
|
|
308
308
|
const body = {
|
|
309
309
|
outTrackId: card.cardInstanceId,
|
|
@@ -229,7 +229,7 @@ export async function sendNormalToUser(
|
|
|
229
229
|
try {
|
|
230
230
|
const token = await getAccessToken(config);
|
|
231
231
|
const body = {
|
|
232
|
-
robotCode: config.clientId,
|
|
232
|
+
robotCode: String(config.clientId),
|
|
233
233
|
userIds: userIdArray,
|
|
234
234
|
msgKey: payload.msgKey,
|
|
235
235
|
msgParam: JSON.stringify(payload.msgParam),
|
|
@@ -306,7 +306,7 @@ export async function sendNormalToGroup(
|
|
|
306
306
|
try {
|
|
307
307
|
const token = await getAccessToken(config);
|
|
308
308
|
const body = {
|
|
309
|
-
robotCode: config.clientId,
|
|
309
|
+
robotCode: String(config.clientId),
|
|
310
310
|
openConversationId,
|
|
311
311
|
msgKey: payload.msgKey,
|
|
312
312
|
msgParam: JSON.stringify(payload.msgParam),
|
|
@@ -439,7 +439,7 @@ export async function sendAICardInternal(
|
|
|
439
439
|
}
|
|
440
440
|
|
|
441
441
|
// 7. 使用 finishAICard 设置内容
|
|
442
|
-
await finishAICard(card, processedContent, log);
|
|
442
|
+
await finishAICard(card, processedContent, config, log);
|
|
443
443
|
|
|
444
444
|
log?.info?.(
|
|
445
445
|
`AI Card 发送成功: ${targetDesc}, cardInstanceId=${card.cardInstanceId}`,
|
|
@@ -767,8 +767,9 @@ export async function sendMediaToDingTalk(params: {
|
|
|
767
767
|
// 获取文件扩展名作为 fileType
|
|
768
768
|
const fileType = ext || "file";
|
|
769
769
|
|
|
770
|
-
//
|
|
770
|
+
// 构建文件信息(path 字段用于 sendFileProactive 中 fileName 的 fallback)
|
|
771
771
|
const fileInfo = {
|
|
772
|
+
path: mediaUrl,
|
|
772
773
|
fileName: fileName,
|
|
773
774
|
fileType: fileType,
|
|
774
775
|
};
|
|
@@ -904,7 +905,7 @@ async function sendProactiveInternal(
|
|
|
904
905
|
try {
|
|
905
906
|
const card = await createAICardForTarget(config, target, externalLog);
|
|
906
907
|
if (card) {
|
|
907
|
-
await finishAICard(card, content, externalLog);
|
|
908
|
+
await finishAICard(card, content, config, externalLog);
|
|
908
909
|
return {
|
|
909
910
|
ok: true,
|
|
910
911
|
cardInstanceId: card.cardInstanceId,
|
|
@@ -956,7 +957,7 @@ async function sendProactiveInternal(
|
|
|
956
957
|
}
|
|
957
958
|
|
|
958
959
|
const body: any = {
|
|
959
|
-
robotCode: config.clientId,
|
|
960
|
+
robotCode: String(config.clientId),
|
|
960
961
|
msgKey: payload.msgKey,
|
|
961
962
|
msgParam: JSON.stringify(payload.msgParam),
|
|
962
963
|
};
|
package/src/utils/http-client.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* HTTP 客户端配置模块
|
|
3
3
|
*
|
|
4
4
|
* 提供统一的 axios 实例,用于钉钉 API 请求。
|
|
5
|
+
* 所有钉钉 API 调用必须使用此模块导出的实例,禁止直接使用 axios 或 fetch。
|
|
5
6
|
*
|
|
6
7
|
* 使用方式:
|
|
7
8
|
* ```typescript
|
|
@@ -13,7 +14,7 @@
|
|
|
13
14
|
|
|
14
15
|
import axios, { type AxiosInstance } from 'axios';
|
|
15
16
|
|
|
16
|
-
/**
|
|
17
|
+
/** 钉钉新版 API 专用 HTTP 客户端(30 秒超时,用于 api.dingtalk.com) */
|
|
17
18
|
export const dingtalkHttp: AxiosInstance = axios.create({
|
|
18
19
|
timeout: 30000,
|
|
19
20
|
headers: {
|