@dingtalk-real-ai/dingtalk-connector 0.8.12 → 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.
Files changed (49) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.en.md +31 -6
  3. package/README.md +31 -6
  4. package/docs/RELEASE_NOTES_V0.7.10.md +40 -0
  5. package/docs/RELEASE_NOTES_V0.7.2.md +143 -0
  6. package/docs/RELEASE_NOTES_V0.7.3.md +149 -0
  7. package/docs/RELEASE_NOTES_V0.7.4.md +206 -0
  8. package/docs/RELEASE_NOTES_V0.7.5.md +267 -0
  9. package/docs/RELEASE_NOTES_V0.7.6.md +219 -0
  10. package/docs/RELEASE_NOTES_V0.7.7.md +122 -0
  11. package/docs/RELEASE_NOTES_V0.7.8.md +101 -0
  12. package/docs/RELEASE_NOTES_V0.7.9.md +65 -0
  13. package/docs/RELEASE_NOTES_V0.8.0.md +53 -0
  14. package/docs/RELEASE_NOTES_V0.8.1.md +47 -0
  15. package/docs/RELEASE_NOTES_V0.8.10.md +49 -0
  16. package/docs/RELEASE_NOTES_V0.8.11.md +51 -0
  17. package/docs/RELEASE_NOTES_V0.8.12.md +63 -0
  18. package/docs/RELEASE_NOTES_V0.8.13-beta.0.md +69 -0
  19. package/docs/RELEASE_NOTES_V0.8.13.md +62 -0
  20. package/docs/RELEASE_NOTES_V0.8.2.md +55 -0
  21. package/docs/RELEASE_NOTES_V0.8.3.md +63 -0
  22. package/docs/RELEASE_NOTES_V0.8.4.md +45 -0
  23. package/docs/RELEASE_NOTES_V0.8.7.md +49 -0
  24. package/docs/RELEASE_NOTES_V0.8.8.md +63 -0
  25. package/docs/RELEASE_NOTES_V0.8.9.md +81 -0
  26. package/docs/RELEASE_NOTES_v0.7.0.md +142 -0
  27. package/docs/RELEASE_NOTES_v0.7.1.md +74 -0
  28. package/openclaw.plugin.json +1 -1
  29. package/package.json +13 -2
  30. package/src/channel.ts +18 -6
  31. package/src/config/schema.ts +2 -2
  32. package/src/core/connection.ts +9 -6
  33. package/src/core/message-handler.ts +30 -10
  34. package/src/reply-dispatcher.ts +4 -3
  35. package/src/services/media/file.ts +7 -2
  36. package/src/services/media.ts +19 -12
  37. package/src/services/messaging/card.ts +1 -2
  38. package/src/services/messaging.ts +29 -16
  39. package/src/utils/http-client.ts +2 -1
  40. package/docs/images/dingtalk.svg +0 -1
  41. package/docs/images/image-1.png +0 -0
  42. package/docs/images/image-2.png +0 -0
  43. package/docs/images/image-3.png +0 -0
  44. package/docs/images/image-4.png +0 -0
  45. package/docs/images/image-5.png +0 -0
  46. package/docs/images/image-6.png +0 -0
  47. package/docs/images/image-7.png +0 -0
  48. package/install-beta.sh +0 -438
  49. package/install-npm.sh +0 -167
@@ -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.cleanMediaId, log);
682
+ await sendFileProactive(config, target, fileInfo, uploadResult.mediaId, log);
678
683
  } else {
679
- await sendFileMessage(config, sessionWebhook, fileInfo, uploadResult.downloadUrl, log);
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: fileInfo.fileName,
966
- fileType: fileInfo.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
  }
@@ -281,7 +281,6 @@ export async function streamAICard(
281
281
  `[DingTalk][AICard] streaming 响应:status=${streamResp.status}`,
282
282
  );
283
283
  } catch (err: any) {
284
- log?.error?.(`[DingTalk][AICard] streaming 更新失败:${err.message}`);
285
284
  throw err;
286
285
  }
287
286
  }
@@ -304,7 +303,7 @@ export async function finishAICard(
304
303
  `[DingTalk][AICard] 开始 finish,最终内容长度=${fixedContent.length}`,
305
304
  );
306
305
 
307
- await streamAICard(card, fixedContent, true, log);
306
+ await streamAICard(card, fixedContent, true, config, log);
308
307
 
309
308
  const body = {
310
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}`,
@@ -552,11 +552,17 @@ export async function sendTextToDingTalk(params: {
552
552
  return { ok: false, error: "Invalid target parameter", usedAICard: false };
553
553
  }
554
554
 
555
- // 判断目标是用户还是群
556
- const isUser = !target.startsWith("cid");
557
- const targetParam = isUser
558
- ? { type: "user" as const, userId: target }
559
- : { type: "group" as const, openConversationId: target };
555
+ // 判断目标是用户还是群(支持 group:/user: 前缀,与 gateway-methods.ts 逻辑保持一致)
556
+ let targetParam: { type: "user"; userId: string } | { type: "group"; openConversationId: string };
557
+ if (target.startsWith("group:")) {
558
+ targetParam = { type: "group", openConversationId: target.slice(6) };
559
+ } else if (target.startsWith("user:")) {
560
+ targetParam = { type: "user", userId: target.slice(5) };
561
+ } else if (target.startsWith("cid")) {
562
+ targetParam = { type: "group", openConversationId: target };
563
+ } else {
564
+ targetParam = { type: "user", userId: target };
565
+ }
560
566
 
561
567
  return sendProactive(config, targetParam, text, {
562
568
  msgType: "text",
@@ -595,11 +601,17 @@ export async function sendMediaToDingTalk(params: {
595
601
  return { ok: false, error: "Invalid target parameter", usedAICard: false };
596
602
  }
597
603
 
598
- // 判断目标是用户还是群
599
- const isUser = !target.startsWith("cid");
600
- const targetParam = isUser
601
- ? { type: "user" as const, userId: target }
602
- : { type: "group" as const, openConversationId: target };
604
+ // 判断目标是用户还是群(支持 group:/user: 前缀,与 gateway-methods.ts 逻辑保持一致)
605
+ let targetParam: { type: "user"; userId: string } | { type: "group"; openConversationId: string };
606
+ if (target.startsWith("group:")) {
607
+ targetParam = { type: "group", openConversationId: target.slice(6) };
608
+ } else if (target.startsWith("user:")) {
609
+ targetParam = { type: "user", userId: target.slice(5) };
610
+ } else if (target.startsWith("cid")) {
611
+ targetParam = { type: "group", openConversationId: target };
612
+ } else {
613
+ targetParam = { type: "user", userId: target };
614
+ }
603
615
 
604
616
  log.info("参数解析完成,mediaUrl:", mediaUrl, "type:", typeof mediaUrl);
605
617
 
@@ -755,8 +767,9 @@ export async function sendMediaToDingTalk(params: {
755
767
  // 获取文件扩展名作为 fileType
756
768
  const fileType = ext || "file";
757
769
 
758
- // 构建文件信息
770
+ // 构建文件信息(path 字段用于 sendFileProactive 中 fileName 的 fallback)
759
771
  const fileInfo = {
772
+ path: mediaUrl,
760
773
  fileName: fileName,
761
774
  fileType: fileType,
762
775
  };
@@ -892,7 +905,7 @@ async function sendProactiveInternal(
892
905
  try {
893
906
  const card = await createAICardForTarget(config, target, externalLog);
894
907
  if (card) {
895
- await finishAICard(card, content, externalLog);
908
+ await finishAICard(card, content, config, externalLog);
896
909
  return {
897
910
  ok: true,
898
911
  cardInstanceId: card.cardInstanceId,
@@ -944,7 +957,7 @@ async function sendProactiveInternal(
944
957
  }
945
958
 
946
959
  const body: any = {
947
- robotCode: config.clientId,
960
+ robotCode: String(config.clientId),
948
961
  msgKey: payload.msgKey,
949
962
  msgParam: JSON.stringify(payload.msgParam),
950
963
  };
@@ -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
- /** 钉钉专用 HTTP 客户端(30 秒超时) */
17
+ /** 钉钉新版 API 专用 HTTP 客户端(30 秒超时,用于 api.dingtalk.com) */
17
18
  export const dingtalkHttp: AxiosInstance = axios.create({
18
19
  timeout: 30000,
19
20
  headers: {
@@ -1 +0,0 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" fill="none" viewBox="0 0 48 48"><path fill="#171A1D" d="M37.05 22.783c-6.758-5.216-14.378-12.128-22.73-19.538-.655-.585-1.242-.354-1.536.42-1.88 4.973-.058 9.386 2.889 11.932s7.368 4.912 10.058 6.155c.105.049.013.203-.093.163-4.953-2.182-8.397-3.765-13.07-7.368-.497-.388-1.01-.242-1.07.521-.384 4.748 2.657 8.483 6.058 9.745 2.1.781 4.398 1.212 6.53 1.474.109.015.084.178-.027.178-2.747.01-6.058-.654-8.935-1.751-.606-.233-.818.25-.722.633.491 2.008 2.974 5.076 6.926 5.73a12 12 0 0 0 2.228.115c.164 0 .208.089.154.217q-2.685 4.6-2.803 4.797c-.091.152-.036.275.156.275h3.543c.164 0 .264.106.18.246l-4.958 8.196c-.191.328.035.565.395.301s15.212-11.133 15.636-11.448c.195-.142.148-.327-.124-.327h-3.18c-.206 0-.252-.14-.111-.28.14-.141 3.602-3.594 4.837-4.888 1.283-1.35 1.938-3.825-.231-5.498"/></svg>
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file