@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 +1 -1
- package/src/api.ts +67 -0
- package/src/monitor.ts +48 -23
package/package.json
CHANGED
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
|
|
1452
|
-
//
|
|
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
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
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
|