@dingtalk-real-ai/dingtalk-connector 0.8.0 → 0.8.1
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 +6 -0
- package/README.en.md +14 -8
- package/README.md +14 -8
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/src/core/connection.ts +49 -10
- package/src/core/message-handler.ts +57 -38
- package/src/reply-dispatcher.ts +14 -1
- package/src/services/media.ts +1 -1
- package/src/services/messaging.ts +30 -36
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,12 @@ 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.1] - 2026-03-20
|
|
9
|
+
|
|
10
|
+
### 修复 / Fixes
|
|
11
|
+
- 🐛 **文件和图片下载 OSS 签名验证失败** - 修复默认 `Content-Type` 请求头导致 OSS 签名验证失败的问题,确保文件和图片能够正常下载
|
|
12
|
+
**File and image download OSS signature verification failure** - Fixed OSS signature verification failure caused by default `Content-Type` header, ensuring files and images download correctly
|
|
13
|
+
|
|
8
14
|
## [0.8.0] - 2026-03-20
|
|
9
15
|
|
|
10
16
|
### 重构 / Refactoring
|
package/README.en.md
CHANGED
|
@@ -268,20 +268,26 @@ Configure multiple bots connected to different agents:
|
|
|
268
268
|
"agents": {
|
|
269
269
|
"list": [
|
|
270
270
|
{
|
|
271
|
-
"
|
|
271
|
+
"id": "ding-bot1",
|
|
272
|
+
"name": "Customer Service Bot",
|
|
272
273
|
"model": "your-model-config",
|
|
273
|
-
"
|
|
274
|
-
|
|
275
|
-
"
|
|
274
|
+
"workspace": "~/.openclaw/workspace-bot1",
|
|
275
|
+
"identity": {
|
|
276
|
+
"name": "Service Assistant",
|
|
277
|
+
"theme": "customer service",
|
|
278
|
+
"emoji": "🤝"
|
|
276
279
|
}
|
|
277
280
|
// Other agent configurations...
|
|
278
281
|
},
|
|
279
282
|
{
|
|
280
|
-
"
|
|
283
|
+
"id": "ding-bot2",
|
|
284
|
+
"name": "Technical Support Bot",
|
|
281
285
|
"model": "your-model-config",
|
|
282
|
-
"
|
|
283
|
-
|
|
284
|
-
"
|
|
286
|
+
"workspace": "~/.openclaw/workspace-bot2",
|
|
287
|
+
"identity": {
|
|
288
|
+
"name": "Tech Expert",
|
|
289
|
+
"theme": "technical support",
|
|
290
|
+
"emoji": "🔧"
|
|
285
291
|
}
|
|
286
292
|
// Other agent configurations...
|
|
287
293
|
}
|
package/README.md
CHANGED
|
@@ -279,20 +279,26 @@ openclaw logs --follow
|
|
|
279
279
|
"agents": {
|
|
280
280
|
"list": [
|
|
281
281
|
{
|
|
282
|
-
"
|
|
282
|
+
"id": "ding-bot1",
|
|
283
|
+
"name": "钉钉客服机器人",
|
|
283
284
|
"model": "your-model-config",
|
|
284
|
-
"
|
|
285
|
-
|
|
286
|
-
"
|
|
285
|
+
"workspace": "~/.openclaw/workspace-bot1",
|
|
286
|
+
"identity": {
|
|
287
|
+
"name": "客服小助手",
|
|
288
|
+
"theme": "专业客服",
|
|
289
|
+
"emoji": "🤝"
|
|
287
290
|
}
|
|
288
291
|
// 其他 agent 配置...
|
|
289
292
|
},
|
|
290
293
|
{
|
|
291
|
-
"
|
|
294
|
+
"id": "ding-bot2",
|
|
295
|
+
"name": "钉钉技术支持机器人",
|
|
292
296
|
"model": "your-model-config",
|
|
293
|
-
"
|
|
294
|
-
|
|
295
|
-
"
|
|
297
|
+
"workspace": "~/.openclaw/workspace-bot2",
|
|
298
|
+
"identity": {
|
|
299
|
+
"name": "技术专家",
|
|
300
|
+
"theme": "技术支持",
|
|
301
|
+
"emoji": "🔧"
|
|
296
302
|
}
|
|
297
303
|
// 其他 agent 配置...
|
|
298
304
|
}
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
package/src/core/connection.ts
CHANGED
|
@@ -138,6 +138,7 @@ export async function monitorSingleAccount(
|
|
|
138
138
|
// ============ 连接状态管理 ============
|
|
139
139
|
|
|
140
140
|
let lastSocketAvailableTime = Date.now();
|
|
141
|
+
let connectionEstablishedTime = Date.now(); // 记录连接建立时间
|
|
141
142
|
let isReconnecting = false;
|
|
142
143
|
let reconnectAttempts = 0;
|
|
143
144
|
let keepAliveTimer: NodeJS.Timeout | null = null;
|
|
@@ -226,18 +227,49 @@ export async function monitorSingleAccount(
|
|
|
226
227
|
// 2. 重新建立连接
|
|
227
228
|
await client.connect();
|
|
228
229
|
|
|
229
|
-
// 3.
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
230
|
+
// 3. 等待连接真正建立(监听 open 事件,最多等待 10 秒)
|
|
231
|
+
const connectionEstablished = await new Promise<boolean>((resolve) => {
|
|
232
|
+
const timeout = setTimeout(() => {
|
|
233
|
+
resolve(false);
|
|
234
|
+
}, 10_000); // 10 秒超时
|
|
235
|
+
|
|
236
|
+
// 如果已经是 OPEN 状态,直接返回
|
|
237
|
+
if (client.socket?.readyState === 1) {
|
|
238
|
+
clearTimeout(timeout);
|
|
239
|
+
resolve(true);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// 否则监听 open 事件
|
|
244
|
+
const onOpen = () => {
|
|
245
|
+
clearTimeout(timeout);
|
|
246
|
+
client.socket?.removeListener('open', onOpen);
|
|
247
|
+
client.socket?.removeListener('error', onError);
|
|
248
|
+
resolve(true);
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
const onError = (err: any) => {
|
|
252
|
+
clearTimeout(timeout);
|
|
253
|
+
client.socket?.removeListener('open', onOpen);
|
|
254
|
+
client.socket?.removeListener('error', onError);
|
|
255
|
+
logger.warn(`连接建立失败: ${err.message}`);
|
|
256
|
+
resolve(false);
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
client.socket?.once('open', onOpen);
|
|
260
|
+
client.socket?.once('error', onError);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
if (!connectionEstablished) {
|
|
264
|
+
throw new Error(`连接建立超时或失败`);
|
|
234
265
|
}
|
|
235
266
|
|
|
236
|
-
// 4. 重置 socket
|
|
267
|
+
// 4. 重置 socket 可用时间、连接建立时间和重连计数
|
|
237
268
|
lastSocketAvailableTime = Date.now();
|
|
269
|
+
connectionEstablishedTime = Date.now(); // 重置连接建立时间
|
|
238
270
|
reconnectAttempts = 0; // 重连成功,重置计数
|
|
239
271
|
|
|
240
|
-
logger.info(`✅ 重连成功 (socket 状态=${
|
|
272
|
+
logger.info(`✅ 重连成功 (socket 状态=${client.socket?.readyState})`);
|
|
241
273
|
} catch (err: any) {
|
|
242
274
|
reconnectAttempts++;
|
|
243
275
|
log?.error?.(
|
|
@@ -329,11 +361,20 @@ export async function monitorSingleAccount(
|
|
|
329
361
|
|
|
330
362
|
// 【心跳检测】检查 socket 状态
|
|
331
363
|
const socketState = client.socket?.readyState;
|
|
364
|
+
const timeSinceConnection = Date.now() - connectionEstablishedTime;
|
|
332
365
|
logger.debug(
|
|
333
|
-
`🔍 心跳检测:socket 状态=${socketState}, elapsed=${Math.round(elapsed / 1000)}s`,
|
|
366
|
+
`🔍 心跳检测:socket 状态=${socketState}, elapsed=${Math.round(elapsed / 1000)}s, 连接已建立=${Math.round(timeSinceConnection / 1000)}s`,
|
|
334
367
|
);
|
|
335
368
|
|
|
369
|
+
// 给新建立的连接 15 秒宽限期,避免在连接建立初期就触发重连
|
|
336
370
|
if (socketState !== 1) {
|
|
371
|
+
if (timeSinceConnection < 15_000) {
|
|
372
|
+
logger.debug(
|
|
373
|
+
`⏳ 连接建立中(已 ${Math.round(timeSinceConnection / 1000)}s),跳过状态检查`,
|
|
374
|
+
);
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
|
|
337
378
|
logger.info(
|
|
338
379
|
`⚠️ 心跳检测:socket 状态=${socketState},触发重连...`,
|
|
339
380
|
);
|
|
@@ -519,8 +560,6 @@ export async function monitorSingleAccount(
|
|
|
519
560
|
|
|
520
561
|
// ===== 第三步:开始处理消息 =====
|
|
521
562
|
logger.info(`🚀 开始处理消息...`);
|
|
522
|
-
logger.info(`AccountId: ${accountId}`);
|
|
523
|
-
logger.info(`HasConfig: ${!!account.config}`);
|
|
524
563
|
|
|
525
564
|
await messageHandler({
|
|
526
565
|
accountId,
|
|
@@ -266,6 +266,11 @@ export async function downloadImageToFile(
|
|
|
266
266
|
try {
|
|
267
267
|
log?.info?.(`开始下载图片: ${downloadUrl.slice(0, 100)}...`);
|
|
268
268
|
const resp = await dingtalkHttp.get(downloadUrl, {
|
|
269
|
+
proxy: false, // 禁用代理,避免 PAC 文件影响
|
|
270
|
+
|
|
271
|
+
headers: {
|
|
272
|
+
'Content-Type': undefined, // 删除默认的 Content-Type 请求头,让 OSS 签名验证通过
|
|
273
|
+
},
|
|
269
274
|
responseType: 'arraybuffer',
|
|
270
275
|
timeout: 30_000,
|
|
271
276
|
});
|
|
@@ -366,6 +371,10 @@ export async function downloadFileToLocal(
|
|
|
366
371
|
try {
|
|
367
372
|
log?.info?.(`开始下载文件: ${fileName}`);
|
|
368
373
|
const resp = await dingtalkHttp.get(downloadUrl, {
|
|
374
|
+
proxy: false, // 禁用代理,避免 PAC 文件影响
|
|
375
|
+
headers: {
|
|
376
|
+
'Content-Type': undefined, // 删除默认的 Content-Type 请求头,让 OSS 签名验证通过
|
|
377
|
+
},
|
|
369
378
|
responseType: 'arraybuffer',
|
|
370
379
|
timeout: 60_000, // 文件可能较大,增加超时时间
|
|
371
380
|
});
|
|
@@ -407,7 +416,8 @@ export async function downloadFileToLocal(
|
|
|
407
416
|
log?.info?.(`文件下载成功: ${fileName}, size=${buffer.length} bytes, path=${localPath}`);
|
|
408
417
|
return localPath;
|
|
409
418
|
} catch (err: any) {
|
|
410
|
-
|
|
419
|
+
console.error(`[ERROR] downloadFileToLocal 异常: ${err.message}`);
|
|
420
|
+
console.error(`[ERROR] 异常堆栈:\n${err.stack}`);
|
|
411
421
|
return null;
|
|
412
422
|
}
|
|
413
423
|
}
|
|
@@ -526,7 +536,7 @@ interface HandleMessageParams {
|
|
|
526
536
|
/**
|
|
527
537
|
* 内部消息处理函数(实际执行消息处理逻辑)
|
|
528
538
|
*/
|
|
529
|
-
async function handleDingTalkMessageInternal(params: HandleMessageParams): Promise<void> {
|
|
539
|
+
export async function handleDingTalkMessageInternal(params: HandleMessageParams): Promise<void> {
|
|
530
540
|
const { accountId, config, data, sessionWebhook, runtime, log, cfg } = params;
|
|
531
541
|
|
|
532
542
|
const content = extractMessageContent(data);
|
|
@@ -825,7 +835,7 @@ async function handleDingTalkMessageInternal(params: HandleMessageParams): Promi
|
|
|
825
835
|
const parseResult = await parseFileContent(localPath, fileName, log);
|
|
826
836
|
|
|
827
837
|
if (parseResult.type === 'text' && parseResult.content) {
|
|
828
|
-
//
|
|
838
|
+
// 文本类文件:将内容注入到上下文(即使解析成功也给出文件路径)
|
|
829
839
|
const contentPreview = parseResult.content.length > 200
|
|
830
840
|
? parseResult.content.slice(0, 200) + '...'
|
|
831
841
|
: parseResult.content;
|
|
@@ -833,6 +843,7 @@ async function handleDingTalkMessageInternal(params: HandleMessageParams): Promi
|
|
|
833
843
|
fileContentParts.push(
|
|
834
844
|
`📄 **${fileType}**: ${fileName}\n` +
|
|
835
845
|
`✅ 已解析文件内容(${parseResult.content.length} 字符)\n` +
|
|
846
|
+
`💾 已保存到本地: ${localPath}\n` +
|
|
836
847
|
`📝 内容预览:\n\`\`\`\n${contentPreview}\n\`\`\`\n\n` +
|
|
837
848
|
`📋 完整内容:\n${parseResult.content}`
|
|
838
849
|
);
|
|
@@ -1191,13 +1202,13 @@ async function handleDingTalkMessageInternal(params: HandleMessageParams): Promi
|
|
|
1191
1202
|
*/
|
|
1192
1203
|
export async function handleDingTalkMessage(params: HandleMessageParams): Promise<void> {
|
|
1193
1204
|
const { accountId, data, log, cfg } = params;
|
|
1194
|
-
|
|
1205
|
+
|
|
1195
1206
|
// 构建会话标识(与会话上下文保持一致)
|
|
1196
1207
|
const isDirect = data.conversationType === '1';
|
|
1197
1208
|
const senderId = data.senderStaffId || data.senderId;
|
|
1198
1209
|
const conversationId = data.conversationId;
|
|
1199
1210
|
const baseSessionId = isDirect ? senderId : conversationId;
|
|
1200
|
-
|
|
1211
|
+
|
|
1201
1212
|
if (!baseSessionId) {
|
|
1202
1213
|
log?.warn?.('无法构建会话标识,跳过队列管理');
|
|
1203
1214
|
return handleDingTalkMessageInternal(params);
|
|
@@ -1206,7 +1217,7 @@ export async function handleDingTalkMessage(params: HandleMessageParams): Promis
|
|
|
1206
1217
|
// 解析 agentId(与消息处理逻辑保持一致)
|
|
1207
1218
|
const chatType = isDirect ? "direct" : "group";
|
|
1208
1219
|
const peerId = isDirect ? senderId : conversationId;
|
|
1209
|
-
|
|
1220
|
+
|
|
1210
1221
|
let matchedAgentId: string | null = null;
|
|
1211
1222
|
if (cfg.bindings && cfg.bindings.length > 0) {
|
|
1212
1223
|
for (const binding of cfg.bindings) {
|
|
@@ -1228,38 +1239,46 @@ export async function handleDingTalkMessage(params: HandleMessageParams): Promis
|
|
|
1228
1239
|
// 构建队列标识:会话 + agentId
|
|
1229
1240
|
// 这样不同 agent 可以并发处理,同一 agent 的同一会话串行处理
|
|
1230
1241
|
const queueKey = `${baseSessionId}:${matchedAgentId}`;
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1242
|
+
|
|
1243
|
+
try {
|
|
1244
|
+
|
|
1245
|
+
// 更新会话活跃时间
|
|
1246
|
+
sessionLastActivity.set(queueKey, Date.now());
|
|
1247
|
+
|
|
1248
|
+
// 获取该会话+agent的上一个处理任务
|
|
1249
|
+
const previousTask = sessionQueues.get(queueKey) || Promise.resolve();
|
|
1250
|
+
|
|
1251
|
+
// 创建当前消息的处理任务
|
|
1252
|
+
const currentTask = previousTask
|
|
1253
|
+
.then(async () => {
|
|
1254
|
+
log?.info?.(`[队列] 开始处理消息,queueKey=${queueKey}`);
|
|
1255
|
+
await handleDingTalkMessageInternal(params);
|
|
1256
|
+
log?.info?.(`[队列] 消息处理完成,queueKey=${queueKey}`);
|
|
1257
|
+
})
|
|
1258
|
+
.catch((err: any) => {
|
|
1259
|
+
log?.error?.(`[队列] 消息处理异常,queueKey=${queueKey}, error=${err.message}`);
|
|
1260
|
+
// 不抛出错误,避免阻塞后续消息
|
|
1261
|
+
})
|
|
1262
|
+
.finally(() => {
|
|
1263
|
+
// 如果当前任务是队列中的最后一个任务,清理队列
|
|
1264
|
+
if (sessionQueues.get(queueKey) === currentTask) {
|
|
1265
|
+
sessionQueues.delete(queueKey);
|
|
1266
|
+
log?.info?.(`[队列] 队列已清空,queueKey=${queueKey}`);
|
|
1267
|
+
}
|
|
1268
|
+
});
|
|
1269
|
+
|
|
1270
|
+
// 更新队列
|
|
1271
|
+
sessionQueues.set(queueKey, currentTask);
|
|
1272
|
+
|
|
1273
|
+
// 等待当前任务完成
|
|
1274
|
+
await currentTask;
|
|
1275
|
+
console.log(`[DEBUG] 任务执行完成`);
|
|
1276
|
+
} catch (err: any) {
|
|
1277
|
+
console.error(`[DEBUG] 队列管理异常: ${err.message}`);
|
|
1278
|
+
console.error(`[DEBUG] 队列管理异常堆栈: ${err.stack}`);
|
|
1279
|
+
// 如果队列管理失败,直接调用内部处理函数
|
|
1280
|
+
return handleDingTalkMessageInternal(params);
|
|
1281
|
+
}
|
|
1263
1282
|
}
|
|
1264
1283
|
|
|
1265
1284
|
// handleDingTalkMessage 已在函数定义处直接导出
|
package/src/reply-dispatcher.ts
CHANGED
|
@@ -276,6 +276,12 @@ export function createDingtalkReplyDispatcher(params: CreateDingtalkReplyDispatc
|
|
|
276
276
|
// 处理媒体标记
|
|
277
277
|
let finalText = accumulatedText;
|
|
278
278
|
|
|
279
|
+
// ✅ 如果累积的文本为空,使用默认提示文案
|
|
280
|
+
if (!finalText.trim()) {
|
|
281
|
+
finalText = '✅ 任务执行完成(无文本输出)';
|
|
282
|
+
log.info(`[DingTalk][closeStreaming] 累积文本为空,使用默认提示文案`);
|
|
283
|
+
}
|
|
284
|
+
|
|
279
285
|
// 获取 oapiToken 用于媒体处理
|
|
280
286
|
const oapiToken = await getOapiAccessToken(account.config as DingtalkConfig);
|
|
281
287
|
|
|
@@ -420,7 +426,14 @@ export function createDingtalkReplyDispatcher(params: CreateDingtalkReplyDispatc
|
|
|
420
426
|
const hasText = Boolean(text.trim());
|
|
421
427
|
const skipTextForDuplicateFinal =
|
|
422
428
|
info?.kind === "final" && hasText && deliveredFinalTexts.has(text);
|
|
423
|
-
|
|
429
|
+
|
|
430
|
+
// ✅ 如果是 final 响应且没有文本,使用默认提示文案
|
|
431
|
+
if (info?.kind === "final" && !hasText) {
|
|
432
|
+
text = '✅ 任务执行完成(无文本输出)';
|
|
433
|
+
log.info(`[DingTalk][deliver] final 响应无文本,使用默认提示文案`);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const shouldDeliverText = Boolean(text.trim()) && !skipTextForDuplicateFinal;
|
|
424
437
|
|
|
425
438
|
if (!shouldDeliverText) {
|
|
426
439
|
log.info(`[DingTalk][deliver] 跳过发送:hasText=${hasText}, skipTextForDuplicateFinal=${skipTextForDuplicateFinal}`);
|
package/src/services/media.ts
CHANGED
|
@@ -7,7 +7,7 @@ import * as fs from 'fs';
|
|
|
7
7
|
import * as path from 'path';
|
|
8
8
|
import type { DingtalkConfig } from '../types/index.ts';
|
|
9
9
|
import { DINGTALK_OAPI, getOapiAccessToken } from '../utils/index.ts';
|
|
10
|
-
import { dingtalkOapiHttp } from '../utils/http-client.ts';
|
|
10
|
+
import { dingtalkHttp, dingtalkOapiHttp } from '../utils/http-client.ts';
|
|
11
11
|
|
|
12
12
|
// ============ 常量 ============
|
|
13
13
|
|
|
@@ -591,9 +591,10 @@ export async function sendMediaToDingTalk(params: {
|
|
|
591
591
|
});
|
|
592
592
|
}
|
|
593
593
|
|
|
594
|
-
// 1.
|
|
595
|
-
|
|
596
|
-
|
|
594
|
+
// 1. 先发送文本消息(如果有且不为空)
|
|
595
|
+
// 注意:只有在 text 有实际内容时才发送,避免发送空消息
|
|
596
|
+
if (text && text.trim().length > 0) {
|
|
597
|
+
log.info("先发送文本消息:", text);
|
|
597
598
|
await sendProactive(config, targetParam, text, {
|
|
598
599
|
msgType: "text",
|
|
599
600
|
replyToId,
|
|
@@ -726,33 +727,28 @@ export async function sendMediaToDingTalk(params: {
|
|
|
726
727
|
};
|
|
727
728
|
}
|
|
728
729
|
|
|
729
|
-
//
|
|
730
|
+
// 对于音频、文件,发送真正的文件消息
|
|
730
731
|
const fs = await import("fs");
|
|
731
732
|
const stats = fs.statSync(mediaUrl);
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
icon = "🎵";
|
|
742
|
-
typeLabel = "音频";
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
const message = `${icon} ${typeLabel}文件已上传\n\n文件: ${fileName}\n大小: ${fileSizeMB} MB\n\n下载链接: ${downloadUrl}`;
|
|
733
|
+
|
|
734
|
+
// 获取文件扩展名作为 fileType
|
|
735
|
+
const fileType = ext || "file";
|
|
736
|
+
|
|
737
|
+
// 构建文件信息
|
|
738
|
+
const fileInfo = {
|
|
739
|
+
fileName: fileName,
|
|
740
|
+
fileType: fileType,
|
|
741
|
+
};
|
|
746
742
|
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
});
|
|
743
|
+
// 使用 sendFileProactive 发送文件消息
|
|
744
|
+
const { sendFileProactive } = await import("./media.ts");
|
|
745
|
+
await sendFileProactive(config, targetParam, fileInfo, uploadResult.mediaId, log);
|
|
751
746
|
|
|
752
|
-
//
|
|
747
|
+
// 返回成功结果
|
|
753
748
|
return {
|
|
754
|
-
|
|
755
|
-
|
|
749
|
+
ok: true,
|
|
750
|
+
usedAICard: false,
|
|
751
|
+
processQueryKey: "file-message-sent",
|
|
756
752
|
};
|
|
757
753
|
} catch (err: any) {
|
|
758
754
|
log.error("发送媒体消息失败:", err.message);
|
|
@@ -916,21 +912,19 @@ async function sendProactiveInternal(
|
|
|
916
912
|
? `${DINGTALK_API}/v1.0/robot/oToMessages/batchSend`
|
|
917
913
|
: `${DINGTALK_API}/v1.0/robot/groupMessages/send`;
|
|
918
914
|
|
|
919
|
-
//
|
|
915
|
+
// 使用 buildMsgPayload 构建消息体(支持所有消息类型)
|
|
916
|
+
const payload = buildMsgPayload(msgType, content, options.title);
|
|
917
|
+
if ("error" in payload) {
|
|
918
|
+
log.error("构建消息失败:", payload.error);
|
|
919
|
+
return { ok: false, error: payload.error, usedAICard: false };
|
|
920
|
+
}
|
|
921
|
+
|
|
920
922
|
const body: any = {
|
|
921
923
|
robotCode: config.clientId,
|
|
922
|
-
msgKey:
|
|
924
|
+
msgKey: payload.msgKey,
|
|
925
|
+
msgParam: JSON.stringify(payload.msgParam),
|
|
923
926
|
};
|
|
924
927
|
|
|
925
|
-
if (msgType === "markdown") {
|
|
926
|
-
body.msgParam = JSON.stringify({
|
|
927
|
-
title: options.title || "Message",
|
|
928
|
-
text: content,
|
|
929
|
-
});
|
|
930
|
-
} else {
|
|
931
|
-
body.msgParam = JSON.stringify({ content });
|
|
932
|
-
}
|
|
933
|
-
|
|
934
928
|
// ✅ 根据目标类型设置不同的参数
|
|
935
929
|
if (isUser) {
|
|
936
930
|
body.userIds = [targetId];
|