@openclaw-china/wecom 0.1.13 → 0.1.15

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/dist/index.js CHANGED
@@ -1,4 +1,6 @@
1
1
  import crypto from 'crypto';
2
+ import * as path from 'path';
3
+ import * as fsPromises from 'fs/promises';
2
4
 
3
5
  var __defProp = Object.defineProperty;
4
6
  var __export = (target, all) => {
@@ -484,8 +486,8 @@ function getErrorMap() {
484
486
 
485
487
  // ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js
486
488
  var makeIssue = (params) => {
487
- const { data, path, errorMaps, issueData } = params;
488
- const fullPath = [...path, ...issueData.path || []];
489
+ const { data, path: path2, errorMaps, issueData } = params;
490
+ const fullPath = [...path2, ...issueData.path || []];
489
491
  const fullIssue = {
490
492
  ...issueData,
491
493
  path: fullPath
@@ -601,11 +603,11 @@ var errorUtil;
601
603
 
602
604
  // ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/types.js
603
605
  var ParseInputLazyPath = class {
604
- constructor(parent, value, path, key) {
606
+ constructor(parent, value, path2, key) {
605
607
  this._cachedPath = [];
606
608
  this.parent = parent;
607
609
  this.data = value;
608
- this._path = path;
610
+ this._path = path2;
609
611
  this._key = key;
610
612
  }
611
613
  get path() {
@@ -4229,6 +4231,70 @@ function checkGroupPolicy(params) {
4229
4231
  return { allowed: true };
4230
4232
  }
4231
4233
 
4234
+ // ../../packages/shared/src/file/file-utils.ts
4235
+ var MIME_TO_EXTENSION = {
4236
+ // Images
4237
+ "image/jpeg": ".jpg",
4238
+ "image/png": ".png",
4239
+ "image/gif": ".gif",
4240
+ "image/webp": ".webp",
4241
+ "image/bmp": ".bmp",
4242
+ // Audio
4243
+ "audio/mpeg": ".mp3",
4244
+ "audio/wav": ".wav",
4245
+ "audio/ogg": ".ogg",
4246
+ "audio/amr": ".amr",
4247
+ "audio/x-m4a": ".m4a",
4248
+ // Video
4249
+ "video/mp4": ".mp4",
4250
+ "video/quicktime": ".mov",
4251
+ "video/x-msvideo": ".avi",
4252
+ "video/webm": ".webm",
4253
+ // Documents
4254
+ "application/pdf": ".pdf",
4255
+ "application/msword": ".doc",
4256
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document": ".docx",
4257
+ "application/vnd.ms-excel": ".xls",
4258
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": ".xlsx",
4259
+ "application/vnd.ms-powerpoint": ".ppt",
4260
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation": ".pptx",
4261
+ "application/rtf": ".rtf",
4262
+ "application/vnd.oasis.opendocument.text": ".odt",
4263
+ "application/vnd.oasis.opendocument.spreadsheet": ".ods",
4264
+ "text/plain": ".txt",
4265
+ "text/markdown": ".md",
4266
+ "text/csv": ".csv",
4267
+ // Archives
4268
+ "application/zip": ".zip",
4269
+ "application/x-rar-compressed": ".rar",
4270
+ "application/vnd.rar": ".rar",
4271
+ "application/x-7z-compressed": ".7z",
4272
+ "application/x-tar": ".tar",
4273
+ "application/gzip": ".gz",
4274
+ "application/x-gzip": ".gz",
4275
+ "application/x-bzip2": ".bz2",
4276
+ // Code
4277
+ "application/json": ".json",
4278
+ "application/xml": ".xml",
4279
+ "text/xml": ".xml",
4280
+ "text/html": ".html",
4281
+ "text/css": ".css",
4282
+ "text/javascript": ".js",
4283
+ "application/javascript": ".js",
4284
+ "text/x-python": ".py",
4285
+ "text/x-java-source": ".java",
4286
+ "text/x-c": ".c",
4287
+ "text/x-yaml": ".yaml",
4288
+ "application/x-yaml": ".yaml"
4289
+ };
4290
+ function resolveExtension(contentType, fileName) {
4291
+ const mimeType = contentType.split(";")[0].trim().toLowerCase();
4292
+ if (mimeType in MIME_TO_EXTENSION) {
4293
+ return MIME_TO_EXTENSION[mimeType];
4294
+ }
4295
+ return ".bin";
4296
+ }
4297
+
4232
4298
  // ../../packages/shared/src/media/media-parser.ts
4233
4299
  var AUDIO_EXTENSIONS = /* @__PURE__ */ new Set([
4234
4300
  "mp3",
@@ -4373,22 +4439,29 @@ function encryptWecomPlaintext(params) {
4373
4439
  const encrypted = Buffer.concat([cipher.update(padded), cipher.final()]);
4374
4440
  return encrypted.toString("base64");
4375
4441
  }
4376
-
4377
- // src/bot.ts
4378
- function resolveSenderId(msg) {
4379
- const userid = msg.from?.userid?.trim();
4380
- return userid || "unknown";
4381
- }
4382
- function resolveChatType(msg) {
4383
- return msg.chattype === "group" ? "group" : "direct";
4384
- }
4385
- function resolveChatId(msg, senderId, chatType) {
4386
- if (chatType === "group") {
4387
- return msg.chatid?.trim() || "unknown";
4442
+ function decryptWecomMedia(params) {
4443
+ const { encryptedBuffer, encodingAESKey } = params;
4444
+ if (!encryptedBuffer || encryptedBuffer.length === 0) {
4445
+ throw new Error("encryptedBuffer cannot be empty");
4446
+ }
4447
+ const aesKey = decodeEncodingAESKey(encodingAESKey);
4448
+ const iv = aesKey.subarray(0, 16);
4449
+ const decipher = crypto.createDecipheriv("aes-256-cbc", aesKey, iv);
4450
+ decipher.setAutoPadding(false);
4451
+ try {
4452
+ const decryptedPadded = Buffer.concat([
4453
+ decipher.update(encryptedBuffer),
4454
+ decipher.final()
4455
+ ]);
4456
+ const decrypted = pkcs7Unpad(decryptedPadded, WECOM_PKCS7_BLOCK_SIZE);
4457
+ return decrypted;
4458
+ } catch (err) {
4459
+ throw new Error(
4460
+ `Failed to decrypt media: ${err instanceof Error ? err.message : String(err)}`
4461
+ );
4388
4462
  }
4389
- return senderId;
4390
4463
  }
4391
- function buildInboundBody(msg) {
4464
+ function extractWecomContent(msg) {
4392
4465
  const msgtype = String(msg.msgtype ?? "").toLowerCase();
4393
4466
  if (msgtype === "text") {
4394
4467
  const content = msg.text?.content;
@@ -4430,6 +4503,19 @@ function buildInboundBody(msg) {
4430
4503
  }
4431
4504
  return msgtype ? `[${msgtype}]` : "";
4432
4505
  }
4506
+ function resolveSenderId(msg) {
4507
+ const userid = msg.from?.userid?.trim();
4508
+ return userid || "unknown";
4509
+ }
4510
+ function resolveChatType(msg) {
4511
+ return msg.chattype === "group" ? "group" : "direct";
4512
+ }
4513
+ function resolveChatId(msg, senderId, chatType) {
4514
+ if (chatType === "group") {
4515
+ return msg.chatid?.trim() || "unknown";
4516
+ }
4517
+ return senderId;
4518
+ }
4433
4519
  async function dispatchWecomMessage(params) {
4434
4520
  const { cfg, account, msg, core, hooks } = params;
4435
4521
  const safeCfg = cfg ?? {};
@@ -4477,85 +4563,311 @@ async function dispatchWecomMessage(params) {
4477
4563
  channel: "wecom",
4478
4564
  peer: { kind: chatType === "group" ? "group" : "dm", id: chatId }
4479
4565
  });
4480
- const rawBody = buildInboundBody(msg);
4481
- const fromLabel = chatType === "group" ? `group:${chatId}` : `user:${senderId}`;
4482
- const storePath = channel.session?.resolveStorePath?.(safeCfg.session?.store, {
4483
- agentId: route.agentId
4566
+ const mediaResult = await processMediaInMessage({
4567
+ msg,
4568
+ encodingAESKey: account.encodingAESKey,
4569
+ log: logger
4484
4570
  });
4485
- const previousTimestamp = channel.session?.readSessionUpdatedAt ? channel.session.readSessionUpdatedAt({
4486
- storePath,
4487
- sessionKey: route.sessionKey
4488
- }) ?? void 0 : void 0;
4489
- const envelopeOptions = channel.reply?.resolveEnvelopeFormatOptions ? channel.reply.resolveEnvelopeFormatOptions(safeCfg) : void 0;
4490
- const body = channel.reply?.formatAgentEnvelope ? channel.reply.formatAgentEnvelope({
4491
- channel: "WeCom",
4492
- from: fromLabel,
4493
- previousTimestamp,
4494
- envelope: envelopeOptions,
4495
- body: rawBody
4496
- }) : rawBody;
4497
- const ctxPayload = channel.reply?.finalizeInboundContext ? channel.reply.finalizeInboundContext({
4498
- Body: body,
4499
- RawBody: rawBody,
4500
- CommandBody: rawBody,
4501
- From: chatType === "group" ? `wecom:group:${chatId}` : `wecom:${senderId}`,
4502
- To: `wecom:${chatId}`,
4503
- SessionKey: route.sessionKey,
4504
- AccountId: route.accountId,
4505
- ChatType: chatType,
4506
- ConversationLabel: fromLabel,
4507
- SenderName: senderId,
4508
- SenderId: senderId,
4509
- Provider: "wecom",
4510
- Surface: "wecom",
4511
- MessageSid: msg.msgid,
4512
- OriginatingChannel: "wecom",
4513
- OriginatingTo: `wecom:${chatId}`
4514
- }) : {
4515
- Body: body,
4516
- RawBody: rawBody,
4517
- CommandBody: rawBody,
4518
- From: chatType === "group" ? `wecom:group:${chatId}` : `wecom:${senderId}`,
4519
- To: `wecom:${chatId}`,
4520
- SessionKey: route.sessionKey,
4521
- AccountId: route.accountId,
4522
- ChatType: chatType,
4523
- ConversationLabel: fromLabel,
4524
- SenderName: senderId,
4525
- SenderId: senderId,
4526
- Provider: "wecom",
4527
- Surface: "wecom",
4528
- MessageSid: msg.msgid,
4529
- OriginatingChannel: "wecom",
4530
- OriginatingTo: `wecom:${chatId}`
4531
- };
4532
- if (channel.session?.recordInboundSession && storePath) {
4533
- await channel.session.recordInboundSession({
4571
+ const rawBody = mediaResult.text;
4572
+ const fromLabel = chatType === "group" ? `group:${chatId}` : `user:${senderId}`;
4573
+ try {
4574
+ const storePath = channel.session?.resolveStorePath?.(safeCfg.session?.store, {
4575
+ agentId: route.agentId
4576
+ });
4577
+ const previousTimestamp = channel.session?.readSessionUpdatedAt ? channel.session.readSessionUpdatedAt({
4534
4578
  storePath,
4535
- sessionKey: ctxPayload.SessionKey ?? route.sessionKey,
4579
+ sessionKey: route.sessionKey
4580
+ }) ?? void 0 : void 0;
4581
+ const envelopeOptions = channel.reply?.resolveEnvelopeFormatOptions ? channel.reply.resolveEnvelopeFormatOptions(safeCfg) : void 0;
4582
+ const body = channel.reply?.formatAgentEnvelope ? channel.reply.formatAgentEnvelope({
4583
+ channel: "WeCom",
4584
+ from: fromLabel,
4585
+ previousTimestamp,
4586
+ envelope: envelopeOptions,
4587
+ body: rawBody
4588
+ }) : rawBody;
4589
+ const ctxPayload = channel.reply?.finalizeInboundContext ? channel.reply.finalizeInboundContext({
4590
+ Body: body,
4591
+ RawBody: rawBody,
4592
+ CommandBody: rawBody,
4593
+ From: chatType === "group" ? `wecom:group:${chatId}` : `wecom:${senderId}`,
4594
+ To: `wecom:${chatId}`,
4595
+ SessionKey: route.sessionKey,
4596
+ AccountId: route.accountId,
4597
+ ChatType: chatType,
4598
+ ConversationLabel: fromLabel,
4599
+ SenderName: senderId,
4600
+ SenderId: senderId,
4601
+ Provider: "wecom",
4602
+ Surface: "wecom",
4603
+ MessageSid: msg.msgid,
4604
+ OriginatingChannel: "wecom",
4605
+ OriginatingTo: `wecom:${chatId}`
4606
+ }) : {
4607
+ Body: body,
4608
+ RawBody: rawBody,
4609
+ CommandBody: rawBody,
4610
+ From: chatType === "group" ? `wecom:group:${chatId}` : `wecom:${senderId}`,
4611
+ To: `wecom:${chatId}`,
4612
+ SessionKey: route.sessionKey,
4613
+ AccountId: route.accountId,
4614
+ ChatType: chatType,
4615
+ ConversationLabel: fromLabel,
4616
+ SenderName: senderId,
4617
+ SenderId: senderId,
4618
+ Provider: "wecom",
4619
+ Surface: "wecom",
4620
+ MessageSid: msg.msgid,
4621
+ OriginatingChannel: "wecom",
4622
+ OriginatingTo: `wecom:${chatId}`
4623
+ };
4624
+ if (channel.session?.recordInboundSession && storePath) {
4625
+ await channel.session.recordInboundSession({
4626
+ storePath,
4627
+ sessionKey: ctxPayload.SessionKey ?? route.sessionKey,
4628
+ ctx: ctxPayload,
4629
+ onRecordError: (err) => {
4630
+ logger.error(`wecom: failed updating session meta: ${String(err)}`);
4631
+ }
4632
+ });
4633
+ }
4634
+ const tableMode = channel.text?.resolveMarkdownTableMode ? channel.text.resolveMarkdownTableMode({ cfg: safeCfg, channel: "wecom", accountId: account.accountId }) : void 0;
4635
+ await channel.reply.dispatchReplyWithBufferedBlockDispatcher({
4536
4636
  ctx: ctxPayload,
4537
- onRecordError: (err) => {
4538
- logger.error(`wecom: failed updating session meta: ${String(err)}`);
4637
+ cfg: safeCfg,
4638
+ dispatcherOptions: {
4639
+ deliver: async (payload) => {
4640
+ const rawText = payload.text ?? "";
4641
+ if (!rawText.trim()) return;
4642
+ const converted = channel.text?.convertMarkdownTables && tableMode ? channel.text.convertMarkdownTables(rawText, tableMode) : rawText;
4643
+ hooks.onChunk(converted);
4644
+ },
4645
+ onError: (err, info) => {
4646
+ hooks.onError?.(err);
4647
+ logger.error(`${info.kind} reply failed: ${String(err)}`);
4648
+ }
4539
4649
  }
4540
4650
  });
4651
+ } finally {
4541
4652
  }
4542
- const tableMode = channel.text?.resolveMarkdownTableMode ? channel.text.resolveMarkdownTableMode({ cfg: safeCfg, channel: "wecom", accountId: account.accountId }) : void 0;
4543
- await channel.reply.dispatchReplyWithBufferedBlockDispatcher({
4544
- ctx: ctxPayload,
4545
- cfg: safeCfg,
4546
- dispatcherOptions: {
4547
- deliver: async (payload) => {
4548
- const rawText = payload.text ?? "";
4549
- if (!rawText.trim()) return;
4550
- const converted = channel.text?.convertMarkdownTables && tableMode ? channel.text.convertMarkdownTables(rawText, tableMode) : rawText;
4551
- hooks.onChunk(converted);
4552
- },
4553
- onError: (err, info) => {
4554
- hooks.onError?.(err);
4555
- logger.error(`${info.kind} reply failed: ${String(err)}`);
4653
+ }
4654
+ var MEDIA_DOWNLOAD_TIMEOUT = 6e4;
4655
+ async function downloadAndDecryptMedia(params) {
4656
+ const { mediaUrl, encodingAESKey, fileName, log } = params;
4657
+ if (!mediaUrl) {
4658
+ throw new Error("mediaUrl is required");
4659
+ }
4660
+ if (!encodingAESKey) {
4661
+ throw new Error("encodingAESKey is required");
4662
+ }
4663
+ log?.debug?.(`[wecom] \u4E0B\u8F7D\u52A0\u5BC6\u5A92\u4F53\u6587\u4EF6: ${mediaUrl.slice(0, 100)}...`);
4664
+ const controller = new AbortController();
4665
+ const timeoutId = setTimeout(() => controller.abort(), MEDIA_DOWNLOAD_TIMEOUT);
4666
+ let encryptedBuffer;
4667
+ let contentType = "application/octet-stream";
4668
+ let contentDisposition = null;
4669
+ try {
4670
+ const response = await fetch(mediaUrl, { signal: controller.signal });
4671
+ if (!response.ok) {
4672
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
4673
+ }
4674
+ contentType = response.headers.get("content-type") || "application/octet-stream";
4675
+ contentDisposition = response.headers.get("content-disposition");
4676
+ const arrayBuffer = await response.arrayBuffer();
4677
+ encryptedBuffer = Buffer.from(arrayBuffer);
4678
+ log?.debug?.(`[wecom] \u4E0B\u8F7D\u5B8C\u6210: ${encryptedBuffer.length} \u5B57\u8282`);
4679
+ } catch (err) {
4680
+ if (err instanceof Error && err.name === "AbortError") {
4681
+ throw new Error(`\u5A92\u4F53\u4E0B\u8F7D\u8D85\u65F6\uFF08${MEDIA_DOWNLOAD_TIMEOUT}ms\uFF09`);
4682
+ }
4683
+ throw err;
4684
+ } finally {
4685
+ clearTimeout(timeoutId);
4686
+ }
4687
+ log?.debug?.(`[wecom] \u89E3\u5BC6\u5A92\u4F53\u6587\u4EF6...`);
4688
+ let decryptedBuffer;
4689
+ try {
4690
+ decryptedBuffer = decryptWecomMedia({
4691
+ encryptedBuffer,
4692
+ encodingAESKey
4693
+ });
4694
+ log?.debug?.(`[wecom] \u89E3\u5BC6\u5B8C\u6210: ${decryptedBuffer.length} \u5B57\u8282`);
4695
+ } catch (err) {
4696
+ throw new Error(`\u89E3\u5BC6\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`);
4697
+ }
4698
+ const sanitizeFileName = (input) => {
4699
+ if (!input) return void 0;
4700
+ const base = path.basename(input);
4701
+ const cleaned = base.replace(/[\\\/]+/g, "_").replace(/[\x00-\x1f\x7f]/g, "").trim();
4702
+ if (!cleaned || cleaned === "." || cleaned === "..") return void 0;
4703
+ return cleaned.length > 200 ? cleaned.slice(0, 200) : cleaned;
4704
+ };
4705
+ let originalFileName = sanitizeFileName(fileName);
4706
+ if (contentDisposition && !originalFileName) {
4707
+ const filenameMatch = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/);
4708
+ if (filenameMatch && filenameMatch[1]) {
4709
+ let headerFileName = filenameMatch[1].replace(/['"]/g, "");
4710
+ try {
4711
+ headerFileName = decodeURIComponent(headerFileName);
4712
+ } catch {
4713
+ }
4714
+ originalFileName = sanitizeFileName(headerFileName);
4715
+ }
4716
+ }
4717
+ let extension = "";
4718
+ if (originalFileName) {
4719
+ const lastDotIndex = originalFileName.lastIndexOf(".");
4720
+ if (lastDotIndex > 0) {
4721
+ extension = originalFileName.slice(lastDotIndex);
4722
+ }
4723
+ }
4724
+ if (!extension) {
4725
+ extension = resolveExtension(contentType);
4726
+ }
4727
+ const now = /* @__PURE__ */ new Date();
4728
+ const yearMonth = now.toISOString().slice(0, 7).replace("-", "");
4729
+ const wecomDir = path.join("/tmp", "wecom", yearMonth);
4730
+ await fsPromises.mkdir(wecomDir, { recursive: true });
4731
+ const baseFileName = originalFileName || `wecom-media`;
4732
+ const baseNameWithoutExt = baseFileName.replace(/\.[-.\w]+$/, "");
4733
+ const timestamp = Date.now();
4734
+ const safeFileName = `${baseNameWithoutExt}-${timestamp}${extension}`;
4735
+ const resolvedDir = path.resolve(wecomDir);
4736
+ const resolvedPath = path.resolve(wecomDir, safeFileName);
4737
+ if (!resolvedPath.startsWith(`${resolvedDir}${path.sep}`) && resolvedPath !== resolvedDir) {
4738
+ throw new Error("Invalid media file path");
4739
+ }
4740
+ await fsPromises.writeFile(resolvedPath, decryptedBuffer);
4741
+ log?.debug?.(`[wecom] \u6587\u4EF6\u5DF2\u4FDD\u5B58: ${resolvedPath}`);
4742
+ return {
4743
+ path: resolvedPath,
4744
+ contentType,
4745
+ size: decryptedBuffer.length,
4746
+ fileName
4747
+ };
4748
+ }
4749
+ async function processMediaInMessage(params) {
4750
+ const { msg, encodingAESKey, log } = params;
4751
+ if (!encodingAESKey) {
4752
+ log?.debug?.(`[wecom] \u672A\u914D\u7F6E encodingAESKey\uFF0C\u8DF3\u8FC7\u5A92\u4F53\u89E3\u5BC6`);
4753
+ return { text: extractWecomContent(msg) };
4754
+ }
4755
+ const msgtype = String(msg.msgtype ?? "").toLowerCase();
4756
+ if (msgtype === "mixed") {
4757
+ const items = msg.mixed?.msg_item;
4758
+ if (Array.isArray(items)) {
4759
+ const processedParts = [];
4760
+ for (const item of items) {
4761
+ if (!item || typeof item !== "object") continue;
4762
+ const typed = item;
4763
+ const t = String(typed.msgtype ?? "").toLowerCase();
4764
+ if (t === "text") {
4765
+ const content = String(typed.text?.content ?? "");
4766
+ processedParts.push(content);
4767
+ } else if (t === "image") {
4768
+ const url = String(typed.image?.url ?? "").trim();
4769
+ if (url) {
4770
+ try {
4771
+ const mediaFile = await downloadAndDecryptMedia({
4772
+ mediaUrl: url,
4773
+ encodingAESKey,
4774
+ fileName: "image.jpg",
4775
+ log
4776
+ });
4777
+ processedParts.push(`[image] ${mediaFile.path}`);
4778
+ } catch (err) {
4779
+ log?.error?.(`[wecom] mixed\u6D88\u606F\u4E2D\u56FE\u7247\u4E0B\u8F7D\u89E3\u5BC6\u5931\u8D25: ${err}`);
4780
+ processedParts.push(`[image] ${url}`);
4781
+ }
4782
+ }
4783
+ } else if (t === "file") {
4784
+ const url = String(typed.file?.url ?? "").trim();
4785
+ const fileName = String(typed.file?.filename ?? "file.bin").trim();
4786
+ if (url) {
4787
+ try {
4788
+ const mediaFile = await downloadAndDecryptMedia({
4789
+ mediaUrl: url,
4790
+ encodingAESKey,
4791
+ fileName,
4792
+ log
4793
+ });
4794
+ processedParts.push(`[file] ${mediaFile.path}`);
4795
+ } catch (err) {
4796
+ log?.error?.(`[wecom] mixed\u6D88\u606F\u4E2D\u6587\u4EF6\u4E0B\u8F7D\u89E3\u5BC6\u5931\u8D25: ${err}`);
4797
+ processedParts.push(`[file] ${url}`);
4798
+ }
4799
+ }
4800
+ } else {
4801
+ processedParts.push(t ? `[${t}]` : "");
4802
+ }
4556
4803
  }
4804
+ return {
4805
+ text: processedParts.filter((p) => Boolean(p && p.trim())).join("\n")
4806
+ };
4557
4807
  }
4558
- });
4808
+ return { text: extractWecomContent(msg) };
4809
+ }
4810
+ if (msgtype === "image") {
4811
+ const url = String(msg.image?.url ?? "").trim();
4812
+ if (url) {
4813
+ try {
4814
+ const mediaFile = await downloadAndDecryptMedia({
4815
+ mediaUrl: url,
4816
+ encodingAESKey,
4817
+ fileName: "image.jpg",
4818
+ // 默认文件名
4819
+ log
4820
+ });
4821
+ return {
4822
+ text: `[image] ${mediaFile.path}`
4823
+ };
4824
+ } catch (err) {
4825
+ log?.error?.(`[wecom] \u56FE\u7247\u4E0B\u8F7D\u89E3\u5BC6\u5931\u8D25: ${err}`);
4826
+ return { text: extractWecomContent(msg) };
4827
+ }
4828
+ }
4829
+ }
4830
+ if (msgtype === "file") {
4831
+ const url = String(msg.file?.url ?? "").trim();
4832
+ const fileName = msg.file?.filename;
4833
+ if (url) {
4834
+ try {
4835
+ const mediaFile = await downloadAndDecryptMedia({
4836
+ mediaUrl: url,
4837
+ encodingAESKey,
4838
+ fileName,
4839
+ log
4840
+ });
4841
+ return {
4842
+ text: `[file] ${mediaFile.path}`
4843
+ };
4844
+ } catch (err) {
4845
+ log?.error?.(`[wecom] \u6587\u4EF6\u4E0B\u8F7D\u89E3\u5BC6\u5931\u8D25: ${err}`);
4846
+ return { text: extractWecomContent(msg) };
4847
+ }
4848
+ }
4849
+ }
4850
+ if (msgtype === "voice") {
4851
+ const url = String(msg.voice?.url ?? "").trim();
4852
+ if (url) {
4853
+ try {
4854
+ const mediaFile = await downloadAndDecryptMedia({
4855
+ mediaUrl: url,
4856
+ encodingAESKey,
4857
+ fileName: "voice.amr",
4858
+ // 默认文件名
4859
+ log
4860
+ });
4861
+ return {
4862
+ text: `[voice] ${mediaFile.path}`
4863
+ };
4864
+ } catch (err) {
4865
+ log?.error?.(`[wecom] \u8BED\u97F3\u4E0B\u8F7D\u89E3\u5BC6\u5931\u8D25: ${err}`);
4866
+ return { text: extractWecomContent(msg) };
4867
+ }
4868
+ }
4869
+ }
4870
+ return { text: extractWecomContent(msg) };
4559
4871
  }
4560
4872
 
4561
4873
  // src/runtime.ts
@@ -4614,11 +4926,11 @@ function jsonOk(res, body) {
4614
4926
  async function readJsonBody(req, maxBytes) {
4615
4927
  const chunks = [];
4616
4928
  let total = 0;
4617
- return await new Promise((resolve) => {
4929
+ return await new Promise((resolve2) => {
4618
4930
  req.on("data", (chunk) => {
4619
4931
  total += chunk.length;
4620
4932
  if (total > maxBytes) {
4621
- resolve({ ok: false, error: "payload too large" });
4933
+ resolve2({ ok: false, error: "payload too large" });
4622
4934
  req.destroy();
4623
4935
  return;
4624
4936
  }
@@ -4628,16 +4940,16 @@ async function readJsonBody(req, maxBytes) {
4628
4940
  try {
4629
4941
  const raw = Buffer.concat(chunks).toString("utf8");
4630
4942
  if (!raw.trim()) {
4631
- resolve({ ok: false, error: "empty payload" });
4943
+ resolve2({ ok: false, error: "empty payload" });
4632
4944
  return;
4633
4945
  }
4634
- resolve({ ok: true, value: JSON.parse(raw) });
4946
+ resolve2({ ok: true, value: JSON.parse(raw) });
4635
4947
  } catch (err) {
4636
- resolve({ ok: false, error: err instanceof Error ? err.message : String(err) });
4948
+ resolve2({ ok: false, error: err instanceof Error ? err.message : String(err) });
4637
4949
  }
4638
4950
  });
4639
4951
  req.on("error", (err) => {
4640
- resolve({ ok: false, error: err instanceof Error ? err.message : String(err) });
4952
+ resolve2({ ok: false, error: err instanceof Error ? err.message : String(err) });
4641
4953
  });
4642
4954
  });
4643
4955
  }
@@ -4705,12 +5017,12 @@ function parseWecomPlainMessage(raw) {
4705
5017
  }
4706
5018
  async function waitForStreamContent(streamId, maxWaitMs) {
4707
5019
  const startedAt = Date.now();
4708
- await new Promise((resolve) => {
5020
+ await new Promise((resolve2) => {
4709
5021
  const tick = () => {
4710
5022
  const state = streams.get(streamId);
4711
- if (!state) return resolve();
4712
- if (state.error || state.finished || state.content.trim()) return resolve();
4713
- if (Date.now() - startedAt >= maxWaitMs) return resolve();
5023
+ if (!state) return resolve2();
5024
+ if (state.error || state.finished || state.content.trim()) return resolve2();
5025
+ if (Date.now() - startedAt >= maxWaitMs) return resolve2();
4714
5026
  setTimeout(tick, 25);
4715
5027
  };
4716
5028
  tick();
@@ -4743,8 +5055,8 @@ function registerWecomWebhookTarget(target) {
4743
5055
  }
4744
5056
  async function handleWecomWebhookRequest(req, res) {
4745
5057
  pruneStreams();
4746
- const path = resolvePath(req);
4747
- const targets = webhookTargets.get(path);
5058
+ const path2 = resolvePath(req);
5059
+ const targets = webhookTargets.get(path2);
4748
5060
  if (!targets || targets.length === 0) return false;
4749
5061
  const query = resolveQueryParams(req);
4750
5062
  const timestamp = query.get("timestamp") ?? "";
@@ -4752,7 +5064,7 @@ async function handleWecomWebhookRequest(req, res) {
4752
5064
  const signature = resolveSignatureParam(query);
4753
5065
  const primary = targets[0];
4754
5066
  const logger = buildLogger(primary);
4755
- logger.debug(`incoming ${req.method} request on ${path} (timestamp=${timestamp}, nonce=${nonce})`);
5067
+ logger.debug(`incoming ${req.method} request on ${path2} (timestamp=${timestamp}, nonce=${nonce})`);
4756
5068
  if (req.method === "GET") {
4757
5069
  const echostr = query.get("echostr") ?? "";
4758
5070
  if (!timestamp || !nonce || !signature || !echostr) {
@@ -5126,7 +5438,7 @@ var wecomPlugin = {
5126
5438
  ctx.setStatus?.({ accountId: ctx.accountId, running: false, configured: false });
5127
5439
  return;
5128
5440
  }
5129
- const path = (account.config.webhookPath ?? "/wecom").trim();
5441
+ const path2 = (account.config.webhookPath ?? "/wecom").trim();
5130
5442
  const unregister = registerWecomWebhookTarget({
5131
5443
  account,
5132
5444
  config: ctx.cfg ?? {},
@@ -5134,18 +5446,18 @@ var wecomPlugin = {
5134
5446
  log: ctx.log?.info ?? console.log,
5135
5447
  error: ctx.log?.error ?? console.error
5136
5448
  },
5137
- path,
5449
+ path: path2,
5138
5450
  statusSink: (patch) => ctx.setStatus?.({ accountId: ctx.accountId, ...patch })
5139
5451
  });
5140
5452
  const existing = unregisterHooks.get(ctx.accountId);
5141
5453
  if (existing) existing();
5142
5454
  unregisterHooks.set(ctx.accountId, unregister);
5143
- ctx.log?.info(`[wecom] webhook registered at ${path} for account ${ctx.accountId}`);
5455
+ ctx.log?.info(`[wecom] webhook registered at ${path2} for account ${ctx.accountId}`);
5144
5456
  ctx.setStatus?.({
5145
5457
  accountId: ctx.accountId,
5146
5458
  running: true,
5147
5459
  configured: true,
5148
- webhookPath: path,
5460
+ webhookPath: path2,
5149
5461
  lastStartAt: Date.now()
5150
5462
  });
5151
5463
  },