@yaoyuanchao/dingtalk 1.7.0 β†’ 1.7.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yaoyuanchao/dingtalk",
3
- "version": "1.7.0",
3
+ "version": "1.7.1",
4
4
  "type": "module",
5
5
  "description": "DingTalk channel plugin for ClawdBot/OpenClaw with Stream Mode support",
6
6
  "license": "MIT",
package/src/api.ts CHANGED
@@ -882,6 +882,73 @@ export async function recallGroupMessages(params: {
882
882
  * Typing indicator helper - sends a "thinking" message that will be recalled
883
883
  * Returns a cleanup function to recall the message
884
884
  */
885
+ // ============================================================================
886
+ // Emotion Reaction API - add/recall emoji reactions on user messages
887
+ // Cleaner UX than typing indicators: reaction floats on the message itself
888
+ // ============================================================================
889
+
890
+ export async function addEmotionReply(params: {
891
+ clientId: string;
892
+ clientSecret: string;
893
+ robotCode: string;
894
+ msgId: string;
895
+ conversationId: string;
896
+ emotionName?: string;
897
+ }): Promise<{ cleanup: () => Promise<void>; error?: string }> {
898
+ const emotionName = params.emotionName || 'πŸ€”ζ€θ€ƒδΈ­';
899
+ try {
900
+ const token = await getDingTalkAccessToken(params.clientId, params.clientSecret);
901
+ const res = await jsonPost(
902
+ `${DINGTALK_API_BASE}/robot/emotion/reply`,
903
+ {
904
+ robotCode: params.robotCode,
905
+ openMsgId: params.msgId,
906
+ openConversationId: params.conversationId,
907
+ emotionType: 2,
908
+ emotionName,
909
+ textEmotion: {
910
+ emotionId: "2659900",
911
+ emotionName,
912
+ text: emotionName,
913
+ backgroundId: "im_bg_1",
914
+ },
915
+ },
916
+ { "x-acs-dingtalk-access-token": token },
917
+ );
918
+
919
+ if (res?.errcode && res.errcode !== 0) {
920
+ return { cleanup: async () => {}, error: `emotion reply failed: ${res.errmsg || res.errcode}` };
921
+ }
922
+
923
+ return {
924
+ cleanup: async () => {
925
+ try {
926
+ const t = await getDingTalkAccessToken(params.clientId, params.clientSecret);
927
+ await jsonPost(
928
+ `${DINGTALK_API_BASE}/robot/emotion/recall`,
929
+ {
930
+ robotCode: params.robotCode,
931
+ openMsgId: params.msgId,
932
+ openConversationId: params.conversationId,
933
+ emotionType: 2,
934
+ emotionName,
935
+ textEmotion: {
936
+ emotionId: "2659900",
937
+ emotionName,
938
+ text: emotionName,
939
+ backgroundId: "im_bg_1",
940
+ },
941
+ },
942
+ { "x-acs-dingtalk-access-token": t },
943
+ );
944
+ } catch (_) { /* best-effort recall */ }
945
+ },
946
+ };
947
+ } catch (err) {
948
+ return { cleanup: async () => {}, error: String(err) };
949
+ }
950
+ }
951
+
885
952
  export async function sendTypingIndicator(params: {
886
953
  clientId: string;
887
954
  clientSecret: string;
package/src/monitor.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { DingTalkRobotMessage, ResolvedDingTalkAccount, ExtractedMessage } from "./types.js";
2
- import { sendViaSessionWebhook, sendMarkdownViaSessionWebhook, sendDingTalkRestMessage, batchGetUserInfo, downloadPicture, downloadMediaFile, cleanupOldMedia, uploadMediaFile, sendFileMessage, textToMarkdownFile, sendTypingIndicator } from "./api.js";
2
+ import { sendViaSessionWebhook, sendMarkdownViaSessionWebhook, sendDingTalkRestMessage, batchGetUserInfo, downloadPicture, downloadMediaFile, cleanupOldMedia, uploadMediaFile, sendFileMessage, textToMarkdownFile, sendTypingIndicator, addEmotionReply } from "./api.js";
3
3
  import { getDingTalkRuntime } from "./runtime.js";
4
4
  import { cacheInboundDownloadCode, getCachedDownloadCode } from "./quoted-msg-cache.js";
5
5
  import { resolveQuotedFile } from "./quoted-file-service.js";
@@ -1448,30 +1448,55 @@ async function dispatchMessageInternal(params: {
1448
1448
  // Typing indicator cleanup function (will be called after dispatch completes)
1449
1449
  let typingCleanup: (() => Promise<void>) | null = null;
1450
1450
 
1451
- // Send typing indicator (recallable) if enabled
1452
- // This replaces the old showThinking feature with a better UX - the indicator disappears when reply arrives
1451
+ // Send thinking feedback: prefer emotion reaction (clean UX, floats on message),
1452
+ // fall back to typing indicator (separate message) if emotion API fails or msgId unavailable.
1453
1453
  if (account.config.typingIndicator !== false && account.clientId && account.clientSecret) {
1454
- try {
1455
- const typingMessage = account.config.typingIndicatorMessage || '⏳ 思考中...';
1456
- const robotCode = account.robotCode || account.clientId;
1457
-
1458
- const result = await sendTypingIndicator({
1459
- clientId: account.clientId,
1460
- clientSecret: account.clientSecret,
1461
- robotCode,
1462
- userId: isDm ? senderId : undefined,
1463
- conversationId: !isDm ? conversationId : undefined,
1464
- message: typingMessage,
1465
- });
1466
-
1467
- if (result.error) {
1468
- log?.info?.('[dingtalk] Typing indicator failed: ' + result.error);
1469
- } else {
1470
- typingCleanup = result.cleanup;
1471
- log?.info?.('[dingtalk] Typing indicator sent (will be recalled on reply)');
1454
+ const robotCode = account.robotCode || account.clientId;
1455
+ let emotionOk = false;
1456
+
1457
+ // Try emotion reaction first (requires msgId + conversationId)
1458
+ if (msg.msgId && conversationId) {
1459
+ try {
1460
+ const result = await addEmotionReply({
1461
+ clientId: account.clientId,
1462
+ clientSecret: account.clientSecret,
1463
+ robotCode,
1464
+ msgId: msg.msgId,
1465
+ conversationId,
1466
+ });
1467
+ if (!result.error) {
1468
+ typingCleanup = result.cleanup;
1469
+ emotionOk = true;
1470
+ log?.info?.('[dingtalk] Emotion reaction added (will be recalled on reply)');
1471
+ } else {
1472
+ log?.info?.('[dingtalk] Emotion reaction failed: ' + result.error + ', falling back to typing indicator');
1473
+ }
1474
+ } catch (err) {
1475
+ log?.info?.('[dingtalk] Emotion reaction error: ' + err + ', falling back to typing indicator');
1476
+ }
1477
+ }
1478
+
1479
+ // Fallback to typing indicator (separate message)
1480
+ if (!emotionOk) {
1481
+ try {
1482
+ const typingMessage = account.config.typingIndicatorMessage || '⏳ 思考中...';
1483
+ const result = await sendTypingIndicator({
1484
+ clientId: account.clientId,
1485
+ clientSecret: account.clientSecret,
1486
+ robotCode,
1487
+ userId: isDm ? senderId : undefined,
1488
+ conversationId: !isDm ? conversationId : undefined,
1489
+ message: typingMessage,
1490
+ });
1491
+ if (!result.error) {
1492
+ typingCleanup = result.cleanup;
1493
+ log?.info?.('[dingtalk] Typing indicator sent (will be recalled on reply)');
1494
+ } else {
1495
+ log?.info?.('[dingtalk] Typing indicator failed: ' + result.error);
1496
+ }
1497
+ } catch (err) {
1498
+ log?.info?.('[dingtalk] Typing indicator error: ' + err);
1472
1499
  }
1473
- } catch (err) {
1474
- log?.info?.('[dingtalk] Typing indicator error: ' + err);
1475
1500
  }
1476
1501
  }
1477
1502
  // Legacy: Send thinking feedback (opt-in, non-recallable) - only if typingIndicator is explicitly disabled