@marshulll/openclaw-wecom 0.1.9 → 0.1.10
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/wecom/src/wecom-bot.ts +62 -26
package/package.json
CHANGED
package/wecom/src/wecom-bot.ts
CHANGED
|
@@ -31,6 +31,17 @@ type StreamState = {
|
|
|
31
31
|
content: string;
|
|
32
32
|
};
|
|
33
33
|
|
|
34
|
+
type InboundMedia = {
|
|
35
|
+
path: string;
|
|
36
|
+
type: string;
|
|
37
|
+
url?: string;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
type InboundBody = {
|
|
41
|
+
text: string;
|
|
42
|
+
media?: InboundMedia;
|
|
43
|
+
};
|
|
44
|
+
|
|
34
45
|
const streams = new Map<string, StreamState>();
|
|
35
46
|
const msgidToStreamId = new Map<string, string>();
|
|
36
47
|
const recentEncrypts = new Map<string, { ts: number; streamId?: string }>();
|
|
@@ -320,7 +331,8 @@ async function startAgentForStream(params: {
|
|
|
320
331
|
const userid = msg.from?.userid?.trim() || "unknown";
|
|
321
332
|
const chatType = msg.chattype === "group" ? "group" : "direct";
|
|
322
333
|
const chatId = msg.chattype === "group" ? (msg.chatid?.trim() || "unknown") : userid;
|
|
323
|
-
const
|
|
334
|
+
const inbound = await buildInboundBody({ target, msg });
|
|
335
|
+
const rawBody = inbound.text;
|
|
324
336
|
|
|
325
337
|
const route = core.channel.routing.resolveAgentRoute({
|
|
326
338
|
cfg: config,
|
|
@@ -367,6 +379,14 @@ async function startAgentForStream(params: {
|
|
|
367
379
|
OriginatingTo: `wecom:${chatId}`,
|
|
368
380
|
});
|
|
369
381
|
|
|
382
|
+
if (inbound.media) {
|
|
383
|
+
ctxPayload.MediaPath = inbound.media.path;
|
|
384
|
+
ctxPayload.MediaType = inbound.media.type;
|
|
385
|
+
if (inbound.media.url) {
|
|
386
|
+
ctxPayload.MediaUrl = inbound.media.url;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
370
390
|
await core.channel.session.recordInboundSession({
|
|
371
391
|
storePath,
|
|
372
392
|
sessionKey: ctxPayload.SessionKey ?? route.sessionKey,
|
|
@@ -550,7 +570,7 @@ async function buildBotMediaMessage(params: {
|
|
|
550
570
|
url?: string;
|
|
551
571
|
base64?: string;
|
|
552
572
|
filename?: string;
|
|
553
|
-
}): Promise<
|
|
573
|
+
}): Promise<InboundBody> {
|
|
554
574
|
const { target, msgtype, url, base64, filename } = params;
|
|
555
575
|
|
|
556
576
|
const fallbackLabel = msgtype === "image"
|
|
@@ -561,28 +581,31 @@ async function buildBotMediaMessage(params: {
|
|
|
561
581
|
? "[video]"
|
|
562
582
|
: "[file]";
|
|
563
583
|
|
|
564
|
-
if (!url && !base64) return fallbackLabel;
|
|
584
|
+
if (!url && !base64) return { text: fallbackLabel };
|
|
565
585
|
|
|
566
586
|
try {
|
|
567
587
|
let buffer: Buffer | null = null;
|
|
568
588
|
let contentType = "";
|
|
569
589
|
if (base64) {
|
|
570
590
|
buffer = Buffer.from(base64, "base64");
|
|
571
|
-
|
|
591
|
+
if (msgtype === "image") contentType = "image/jpeg";
|
|
592
|
+
else if (msgtype === "voice") contentType = "audio/amr";
|
|
593
|
+
else if (msgtype === "video") contentType = "video/mp4";
|
|
594
|
+
else contentType = "application/octet-stream";
|
|
572
595
|
} else if (url) {
|
|
573
596
|
const media = await fetchMediaFromUrl(url, target.account);
|
|
574
597
|
buffer = media.buffer;
|
|
575
598
|
contentType = media.contentType;
|
|
576
599
|
}
|
|
577
600
|
|
|
578
|
-
if (!buffer) return fallbackLabel;
|
|
601
|
+
if (!buffer) return { text: fallbackLabel };
|
|
579
602
|
|
|
580
603
|
const maxBytes = resolveMediaMaxBytes(target);
|
|
581
604
|
if (maxBytes && buffer.length > maxBytes) {
|
|
582
|
-
if (msgtype === "image") return "[图片过大,未处理]\n\n请发送更小的图片。";
|
|
583
|
-
if (msgtype === "voice") return "[语音消息过大,未处理]\n\n请发送更短的语音消息。";
|
|
584
|
-
if (msgtype === "video") return "[视频过大,未处理]\n\n请发送更小的视频。";
|
|
585
|
-
return "[文件过大,未处理]\n\n请发送更小的文件。";
|
|
605
|
+
if (msgtype === "image") return { text: "[图片过大,未处理]\n\n请发送更小的图片。" };
|
|
606
|
+
if (msgtype === "voice") return { text: "[语音消息过大,未处理]\n\n请发送更短的语音消息。" };
|
|
607
|
+
if (msgtype === "video") return { text: "[视频过大,未处理]\n\n请发送更小的视频。" };
|
|
608
|
+
return { text: "[文件过大,未处理]\n\n请发送更小的文件。" };
|
|
586
609
|
}
|
|
587
610
|
|
|
588
611
|
const tempDir = resolveMediaTempDir(target);
|
|
@@ -606,7 +629,10 @@ async function buildBotMediaMessage(params: {
|
|
|
606
629
|
const safeName = sanitizeFilename(filename || "", `file-${Date.now()}.${ext}`);
|
|
607
630
|
const tempFilePath = join(tempDir, safeName);
|
|
608
631
|
await writeFile(tempFilePath, buffer);
|
|
609
|
-
return
|
|
632
|
+
return {
|
|
633
|
+
text: `[用户发送了一个文件: ${safeName},已保存到: ${tempFilePath}]\n\n请根据文件内容回复用户。`,
|
|
634
|
+
media: { path: tempFilePath, type: contentType || "application/octet-stream", url },
|
|
635
|
+
};
|
|
610
636
|
}
|
|
611
637
|
|
|
612
638
|
const tempPath = join(
|
|
@@ -616,34 +642,43 @@ async function buildBotMediaMessage(params: {
|
|
|
616
642
|
await writeFile(tempPath, buffer);
|
|
617
643
|
|
|
618
644
|
if (msgtype === "image") {
|
|
619
|
-
return
|
|
645
|
+
return {
|
|
646
|
+
text: `[用户发送了一张图片,已保存到: ${tempPath}]\n\n请使用 Read 工具查看这张图片并描述内容。`,
|
|
647
|
+
media: { path: tempPath, type: contentType || "image/jpeg", url },
|
|
648
|
+
};
|
|
620
649
|
}
|
|
621
650
|
if (msgtype === "voice") {
|
|
622
|
-
return
|
|
651
|
+
return {
|
|
652
|
+
text: `[用户发送了一条语音消息,已保存到: ${tempPath}]\n\n请根据语音内容回复用户。`,
|
|
653
|
+
media: { path: tempPath, type: contentType || "audio/amr", url },
|
|
654
|
+
};
|
|
623
655
|
}
|
|
624
656
|
if (msgtype === "video") {
|
|
625
|
-
return
|
|
657
|
+
return {
|
|
658
|
+
text: `[用户发送了一个视频文件,已保存到: ${tempPath}]\n\n请根据视频内容回复用户。`,
|
|
659
|
+
media: { path: tempPath, type: contentType || "video/mp4", url },
|
|
660
|
+
};
|
|
626
661
|
}
|
|
627
|
-
return fallbackLabel;
|
|
662
|
+
return { text: fallbackLabel };
|
|
628
663
|
} catch (err) {
|
|
629
664
|
target.runtime.error?.(`wecom bot ${msgtype} download failed: ${String(err)}`);
|
|
630
|
-
if (msgtype === "image") return "[用户发送了一张图片,但下载失败]\n\n请告诉用户图片处理暂时不可用。";
|
|
631
|
-
if (msgtype === "voice") return "[用户发送了一条语音消息,但下载失败]\n\n请告诉用户语音处理暂时不可用。";
|
|
632
|
-
if (msgtype === "video") return "[用户发送了一个视频,但下载失败]\n\n请告诉用户视频处理暂时不可用。";
|
|
633
|
-
return "[用户发送了一个文件,但下载失败]\n\n请告诉用户文件处理暂时不可用。";
|
|
665
|
+
if (msgtype === "image") return { text: "[用户发送了一张图片,但下载失败]\n\n请告诉用户图片处理暂时不可用。" };
|
|
666
|
+
if (msgtype === "voice") return { text: "[用户发送了一条语音消息,但下载失败]\n\n请告诉用户语音处理暂时不可用。" };
|
|
667
|
+
if (msgtype === "video") return { text: "[用户发送了一个视频,但下载失败]\n\n请告诉用户视频处理暂时不可用。" };
|
|
668
|
+
return { text: "[用户发送了一个文件,但下载失败]\n\n请告诉用户文件处理暂时不可用。" };
|
|
634
669
|
}
|
|
635
670
|
}
|
|
636
671
|
|
|
637
|
-
async function buildInboundBody(params: { target: WecomWebhookTarget; msg: WecomInboundMessage }): Promise<
|
|
672
|
+
async function buildInboundBody(params: { target: WecomWebhookTarget; msg: WecomInboundMessage }): Promise<InboundBody> {
|
|
638
673
|
const { target, msg } = params;
|
|
639
674
|
const msgtype = String(msg.msgtype ?? "").toLowerCase();
|
|
640
675
|
if (msgtype === "text") {
|
|
641
676
|
const content = (msg as any).text?.content;
|
|
642
|
-
return typeof content === "string" ? content : "";
|
|
677
|
+
return { text: typeof content === "string" ? content : "" };
|
|
643
678
|
}
|
|
644
679
|
if (msgtype === "voice") {
|
|
645
680
|
const content = (msg as any).voice?.content;
|
|
646
|
-
if (typeof content === "string" && content.trim()) return content;
|
|
681
|
+
if (typeof content === "string" && content.trim()) return { text: content.trim() };
|
|
647
682
|
const url = resolveBotMediaUrl(msg as any, "voice");
|
|
648
683
|
const base64 = resolveBotMediaBase64(msg as any, "voice");
|
|
649
684
|
return await buildBotMediaMessage({ target, msgtype: "voice", url, base64 });
|
|
@@ -651,7 +686,7 @@ async function buildInboundBody(params: { target: WecomWebhookTarget; msg: Wecom
|
|
|
651
686
|
if (msgtype === "mixed") {
|
|
652
687
|
const items = (msg as any).mixed?.msg_item;
|
|
653
688
|
if (Array.isArray(items)) {
|
|
654
|
-
|
|
689
|
+
const text = items
|
|
655
690
|
.map((item: any) => {
|
|
656
691
|
const t = String(item?.msgtype ?? "").toLowerCase();
|
|
657
692
|
if (t === "text") return String(item?.text?.content ?? "");
|
|
@@ -660,8 +695,9 @@ async function buildInboundBody(params: { target: WecomWebhookTarget; msg: Wecom
|
|
|
660
695
|
})
|
|
661
696
|
.filter((part: string) => Boolean(part && part.trim()))
|
|
662
697
|
.join("\n");
|
|
698
|
+
return { text };
|
|
663
699
|
}
|
|
664
|
-
return "[mixed]";
|
|
700
|
+
return { text: "[mixed]" };
|
|
665
701
|
}
|
|
666
702
|
if (msgtype === "image") {
|
|
667
703
|
const url = resolveBotMediaUrl(msg as any, "image");
|
|
@@ -681,13 +717,13 @@ async function buildInboundBody(params: { target: WecomWebhookTarget; msg: Wecom
|
|
|
681
717
|
}
|
|
682
718
|
if (msgtype === "event") {
|
|
683
719
|
const eventtype = String((msg as any).event?.eventtype ?? "").trim();
|
|
684
|
-
return eventtype ? `[event] ${eventtype}` : "[event]";
|
|
720
|
+
return { text: eventtype ? `[event] ${eventtype}` : "[event]" };
|
|
685
721
|
}
|
|
686
722
|
if (msgtype === "stream") {
|
|
687
723
|
const id = String((msg as any).stream?.id ?? "").trim();
|
|
688
|
-
return id ? `[stream_refresh] ${id}` : "[stream_refresh]";
|
|
724
|
+
return { text: id ? `[stream_refresh] ${id}` : "[stream_refresh]" };
|
|
689
725
|
}
|
|
690
|
-
return msgtype ? `[${msgtype}]` : "";
|
|
726
|
+
return { text: msgtype ? `[${msgtype}]` : "" };
|
|
691
727
|
}
|
|
692
728
|
|
|
693
729
|
function normalizeMediaType(raw?: string): "image" | "voice" | "video" | "file" | null {
|