@openclaw-china/wecom 0.1.28 → 2026.3.2

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
@@ -6112,14 +6112,6 @@ async function configureQQBot(prompter, cfg) {
6112
6112
  existingValue: toTrimmedString(existing.clientSecret),
6113
6113
  required: true
6114
6114
  });
6115
- Ve(
6116
- "QQ \u7684 Markdown \u4F53\u9A8C\u5F88\u597D\uFF0C\u4F46\u9700\u8981\u5148\u7533\u8BF7\u5F00\u901A\uFF0C\u8BE6\u60C5\u8BF7\u67E5\u770B\u914D\u7F6E\u6587\u6863\u3002",
6117
- "\u63D0\u793A"
6118
- );
6119
- const markdownSupport = await prompter.askConfirm(
6120
- "\u542F\u7528 Markdown \u652F\u6301",
6121
- toBoolean(existing.markdownSupport, false)
6122
- );
6123
6115
  const asrEnabled = await prompter.askConfirm(
6124
6116
  "\u542F\u7528 ASR\uFF08\u652F\u6301\u5165\u7AD9\u8BED\u97F3\u81EA\u52A8\u8F6C\u6587\u5B57\uFF09",
6125
6117
  toBoolean(existingAsr.enabled, false)
@@ -6148,7 +6140,6 @@ async function configureQQBot(prompter, cfg) {
6148
6140
  return mergeChannelConfig(cfg, "qqbot", {
6149
6141
  appId,
6150
6142
  clientSecret,
6151
- markdownSupport,
6152
6143
  asr
6153
6144
  });
6154
6145
  }
@@ -7344,7 +7335,156 @@ function jsonOk(res, body) {
7344
7335
  res.setHeader("Content-Type", "text/plain; charset=utf-8");
7345
7336
  res.end(JSON.stringify(body));
7346
7337
  }
7347
- async function readJsonBody(req, maxBytes) {
7338
+ function extractXmlTag(xml, tag) {
7339
+ if (!xml || !tag) return void 0;
7340
+ const escapedTag = tag.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7341
+ const re = new RegExp(`<${escapedTag}(?:\\s[^>]*)?>([\\s\\S]*?)</${escapedTag}>`, "i");
7342
+ const m = re.exec(xml);
7343
+ if (!m) return void 0;
7344
+ const body = m[1] ?? "";
7345
+ const cdata = /^<!\[CDATA\[([\s\S]*?)\]\]>$/i.exec(body.trim());
7346
+ if (cdata) return cdata[1] ?? "";
7347
+ return body;
7348
+ }
7349
+ function extractXmlTagAll(xml, tag) {
7350
+ if (!xml || !tag) return [];
7351
+ const escapedTag = tag.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7352
+ const re = new RegExp(`<${escapedTag}(?:\\s[^>]*)?>([\\s\\S]*?)</${escapedTag}>`, "gi");
7353
+ const out = [];
7354
+ let m;
7355
+ while ((m = re.exec(xml)) !== null) {
7356
+ const body = m[1] ?? "";
7357
+ const cdata = /^<!\[CDATA\[([\s\S]*?)\]\]>$/i.exec(body.trim());
7358
+ out.push(cdata ? cdata[1] ?? "" : body);
7359
+ }
7360
+ return out;
7361
+ }
7362
+ function pickFirstNonEmpty(...values) {
7363
+ for (const value of values) {
7364
+ const trimmed = value?.trim();
7365
+ if (trimmed) return trimmed;
7366
+ }
7367
+ return "";
7368
+ }
7369
+ function parseXmlTextPayload(xml) {
7370
+ const textBlock = extractXmlTag(xml, "Text") ?? "";
7371
+ const content = pickFirstNonEmpty(
7372
+ extractXmlTag(textBlock, "Content"),
7373
+ extractXmlTag(xml, "Content")
7374
+ );
7375
+ if (!content) return void 0;
7376
+ return { content };
7377
+ }
7378
+ function parseXmlVoicePayload(xml) {
7379
+ const voiceBlock = extractXmlTag(xml, "Voice") ?? "";
7380
+ const content = pickFirstNonEmpty(
7381
+ extractXmlTag(voiceBlock, "Content"),
7382
+ extractXmlTag(voiceBlock, "Recognition"),
7383
+ extractXmlTag(xml, "Recognition"),
7384
+ extractXmlTag(xml, "Content")
7385
+ );
7386
+ const url = pickFirstNonEmpty(
7387
+ extractXmlTag(voiceBlock, "Url"),
7388
+ extractXmlTag(voiceBlock, "VoiceUrl"),
7389
+ extractXmlTag(xml, "VoiceUrl")
7390
+ );
7391
+ const mediaId = pickFirstNonEmpty(
7392
+ extractXmlTag(voiceBlock, "MediaId"),
7393
+ extractXmlTag(xml, "MediaId")
7394
+ );
7395
+ if (!content && !url && !mediaId) return void 0;
7396
+ const voice = {};
7397
+ if (content) voice.content = content;
7398
+ if (url) voice.url = url;
7399
+ if (mediaId) voice.media_id = mediaId;
7400
+ return voice;
7401
+ }
7402
+ function parseXmlImagePayload(xml) {
7403
+ const imageBlock = extractXmlTag(xml, "Image") ?? "";
7404
+ const url = pickFirstNonEmpty(
7405
+ extractXmlTag(imageBlock, "Url"),
7406
+ extractXmlTag(imageBlock, "PicUrl"),
7407
+ extractXmlTag(xml, "PicUrl"),
7408
+ extractXmlTag(xml, "Url")
7409
+ );
7410
+ const mediaId = pickFirstNonEmpty(
7411
+ extractXmlTag(imageBlock, "MediaId"),
7412
+ extractXmlTag(xml, "MediaId")
7413
+ );
7414
+ if (!url && !mediaId) return void 0;
7415
+ const image = {};
7416
+ if (url) image.url = url;
7417
+ if (mediaId) image.media_id = mediaId;
7418
+ return image;
7419
+ }
7420
+ function parseXmlFilePayload(xml) {
7421
+ const fileBlock = extractXmlTag(xml, "File") ?? "";
7422
+ const url = pickFirstNonEmpty(
7423
+ extractXmlTag(fileBlock, "Url"),
7424
+ extractXmlTag(fileBlock, "FileUrl"),
7425
+ extractXmlTag(xml, "FileUrl"),
7426
+ extractXmlTag(xml, "Url")
7427
+ );
7428
+ const fileName = pickFirstNonEmpty(
7429
+ extractXmlTag(fileBlock, "FileName"),
7430
+ extractXmlTag(fileBlock, "Name"),
7431
+ extractXmlTag(xml, "FileName")
7432
+ );
7433
+ const mediaId = pickFirstNonEmpty(
7434
+ extractXmlTag(fileBlock, "MediaId"),
7435
+ extractXmlTag(xml, "MediaId")
7436
+ );
7437
+ if (!url && !fileName && !mediaId) return void 0;
7438
+ const file = {};
7439
+ if (url) file.url = url;
7440
+ if (fileName) file.filename = fileName;
7441
+ if (mediaId) file.media_id = mediaId;
7442
+ return file;
7443
+ }
7444
+ function parseXmlMixedItems(xml) {
7445
+ const mixedBlock = extractXmlTag(xml, "Mixed");
7446
+ if (!mixedBlock) return [];
7447
+ const itemBlocks = [
7448
+ ...extractXmlTagAll(mixedBlock, "MsgItem"),
7449
+ ...extractXmlTagAll(mixedBlock, "msg_item")
7450
+ ];
7451
+ if (itemBlocks.length === 0) return [];
7452
+ const items = [];
7453
+ for (const itemBlock of itemBlocks) {
7454
+ const itemType = pickFirstNonEmpty(
7455
+ extractXmlTag(itemBlock, "MsgType"),
7456
+ extractXmlTag(itemBlock, "msgtype"),
7457
+ extractXmlTag(itemBlock, "Type")
7458
+ ).toLowerCase();
7459
+ if (!itemType) continue;
7460
+ if (itemType === "text") {
7461
+ const text = parseXmlTextPayload(itemBlock);
7462
+ items.push({ msgtype: "text", text: text ?? { content: "" } });
7463
+ continue;
7464
+ }
7465
+ if (itemType === "image") {
7466
+ const image = parseXmlImagePayload(itemBlock);
7467
+ if (image) items.push({ msgtype: "image", image });
7468
+ else items.push({ msgtype: "image" });
7469
+ continue;
7470
+ }
7471
+ if (itemType === "file") {
7472
+ const file = parseXmlFilePayload(itemBlock);
7473
+ if (file) items.push({ msgtype: "file", file });
7474
+ else items.push({ msgtype: "file" });
7475
+ continue;
7476
+ }
7477
+ if (itemType === "voice") {
7478
+ const voice = parseXmlVoicePayload(itemBlock);
7479
+ if (voice) items.push({ msgtype: "voice", voice });
7480
+ else items.push({ msgtype: "voice" });
7481
+ continue;
7482
+ }
7483
+ items.push({ msgtype: itemType });
7484
+ }
7485
+ return items;
7486
+ }
7487
+ async function readRequestBody(req, maxBytes) {
7348
7488
  const chunks = [];
7349
7489
  let total = 0;
7350
7490
  return await new Promise((resolve3) => {
@@ -7364,7 +7504,17 @@ async function readJsonBody(req, maxBytes) {
7364
7504
  resolve3({ ok: false, error: "empty payload" });
7365
7505
  return;
7366
7506
  }
7367
- resolve3({ ok: true, value: JSON.parse(raw) });
7507
+ const trimmed = raw.trim();
7508
+ if (trimmed.startsWith("<")) {
7509
+ const encrypt = pickFirstNonEmpty(extractXmlTag(trimmed, "Encrypt"));
7510
+ if (encrypt) {
7511
+ resolve3({ ok: true, value: { Encrypt: encrypt }, raw });
7512
+ } else {
7513
+ resolve3({ ok: false, raw, error: "xml body missing Encrypt tag" });
7514
+ }
7515
+ return;
7516
+ }
7517
+ resolve3({ ok: true, value: JSON.parse(raw), raw });
7368
7518
  } catch (err) {
7369
7519
  resolve3({ ok: false, error: err instanceof Error ? err.message : String(err) });
7370
7520
  }
@@ -7433,11 +7583,94 @@ function createStreamId() {
7433
7583
  return crypto.randomBytes(16).toString("hex");
7434
7584
  }
7435
7585
  function parseWecomPlainMessage(raw) {
7436
- const parsed = JSON.parse(raw);
7437
- if (!parsed || typeof parsed !== "object") {
7438
- return {};
7586
+ const trimmed = raw.trim();
7587
+ if (trimmed.startsWith("<")) {
7588
+ return parseWecomXmlMessage(trimmed);
7589
+ }
7590
+ try {
7591
+ const parsed = JSON.parse(raw);
7592
+ if (!parsed || typeof parsed !== "object") {
7593
+ return {};
7594
+ }
7595
+ return parsed;
7596
+ } catch {
7597
+ return parseWecomXmlMessage(trimmed);
7598
+ }
7599
+ }
7600
+ function parseWecomXmlMessage(xml) {
7601
+ const msgtype = pickFirstNonEmpty(
7602
+ extractXmlTag(xml, "MsgType"),
7603
+ extractXmlTag(xml, "msgtype")
7604
+ ).toLowerCase();
7605
+ const chattype = pickFirstNonEmpty(
7606
+ extractXmlTag(xml, "ChatType"),
7607
+ extractXmlTag(xml, "chattype")
7608
+ ).toLowerCase();
7609
+ const chatid = pickFirstNonEmpty(
7610
+ extractXmlTag(xml, "ChatId"),
7611
+ extractXmlTag(xml, "chatid")
7612
+ );
7613
+ const msgid = pickFirstNonEmpty(
7614
+ extractXmlTag(xml, "MsgId"),
7615
+ extractXmlTag(xml, "msgid")
7616
+ );
7617
+ const webhookUrl = pickFirstNonEmpty(
7618
+ extractXmlTag(xml, "WebhookUrl"),
7619
+ extractXmlTag(xml, "response_url")
7620
+ );
7621
+ const fromBlock = extractXmlTag(xml, "From") ?? "";
7622
+ const userid = pickFirstNonEmpty(
7623
+ extractXmlTag(fromBlock, "UserId"),
7624
+ extractXmlTag(xml, "FromUserName"),
7625
+ extractXmlTag(xml, "UserId")
7626
+ );
7627
+ const result = {
7628
+ msgtype,
7629
+ chattype: chattype === "group" ? "group" : "single",
7630
+ chatid,
7631
+ msgid,
7632
+ from: { userid }
7633
+ };
7634
+ if (webhookUrl) {
7635
+ result.response_url = webhookUrl;
7439
7636
  }
7440
- return parsed;
7637
+ if (msgtype === "text") {
7638
+ result.text = parseXmlTextPayload(xml) ?? { content: "" };
7639
+ } else if (msgtype === "voice") {
7640
+ result.voice = parseXmlVoicePayload(xml) ?? { content: "" };
7641
+ } else if (msgtype === "image") {
7642
+ const image = parseXmlImagePayload(xml);
7643
+ if (image) result.image = image;
7644
+ } else if (msgtype === "file") {
7645
+ const file = parseXmlFilePayload(xml);
7646
+ if (file) result.file = file;
7647
+ } else if (msgtype === "stream") {
7648
+ const streamBlock = extractXmlTag(xml, "Stream") ?? "";
7649
+ const id = pickFirstNonEmpty(
7650
+ extractXmlTag(streamBlock, "Id"),
7651
+ extractXmlTag(xml, "StreamId"),
7652
+ extractXmlTag(xml, "Id")
7653
+ );
7654
+ result.stream = { id };
7655
+ } else if (msgtype === "event") {
7656
+ const eventBlock = extractXmlTag(xml, "Event") ?? "";
7657
+ const eventtype = pickFirstNonEmpty(
7658
+ extractXmlTag(eventBlock, "EventType"),
7659
+ extractXmlTag(xml, "EventType"),
7660
+ extractXmlTag(xml, "Event")
7661
+ );
7662
+ result.event = { eventtype };
7663
+ } else if (msgtype === "mixed") {
7664
+ const mixedItems = parseXmlMixedItems(xml);
7665
+ if (mixedItems.length > 0) {
7666
+ result.mixed = { msg_item: mixedItems };
7667
+ }
7668
+ const text = parseXmlTextPayload(xml);
7669
+ if (text) {
7670
+ result.text = text;
7671
+ }
7672
+ }
7673
+ return result;
7441
7674
  }
7442
7675
  async function waitForStreamContent(streamId, maxWaitMs) {
7443
7676
  const startedAt = Date.now();
@@ -7618,14 +7851,17 @@ async function handleWecomWebhookRequest(req, res) {
7618
7851
  return true;
7619
7852
  }
7620
7853
  const target2 = targets.find((candidate) => {
7621
- if (!candidate.account.configured || !candidate.account.token) return false;
7622
- return verifyWecomSignature({
7854
+ if (!candidate.account.configured || !candidate.account.token) {
7855
+ return false;
7856
+ }
7857
+ const ok = verifyWecomSignature({
7623
7858
  token: candidate.account.token,
7624
7859
  timestamp,
7625
7860
  nonce,
7626
7861
  encrypt: echostr,
7627
7862
  signature
7628
7863
  });
7864
+ return ok;
7629
7865
  });
7630
7866
  if (!target2 || !target2.account.encodingAESKey) {
7631
7867
  res.statusCode = 401;
@@ -7661,7 +7897,7 @@ async function handleWecomWebhookRequest(req, res) {
7661
7897
  res.end("missing query params");
7662
7898
  return true;
7663
7899
  }
7664
- const body = await readJsonBody(req, 1024 * 1024);
7900
+ const body = await readRequestBody(req, 1024 * 1024);
7665
7901
  if (!body.ok) {
7666
7902
  res.statusCode = body.error === "payload too large" ? 413 : 400;
7667
7903
  res.end(body.error ?? "invalid payload");
@@ -7675,14 +7911,17 @@ async function handleWecomWebhookRequest(req, res) {
7675
7911
  return true;
7676
7912
  }
7677
7913
  const target = targets.find((candidate) => {
7678
- if (!candidate.account.token) return false;
7679
- return verifyWecomSignature({
7914
+ if (!candidate.account.token) {
7915
+ return false;
7916
+ }
7917
+ const ok = verifyWecomSignature({
7680
7918
  token: candidate.account.token,
7681
7919
  timestamp,
7682
7920
  nonce,
7683
7921
  encrypt,
7684
7922
  signature
7685
7923
  });
7924
+ return ok;
7686
7925
  });
7687
7926
  if (!target) {
7688
7927
  res.statusCode = 401;
@@ -7720,15 +7959,13 @@ async function handleWecomWebhookRequest(req, res) {
7720
7959
  finished: true,
7721
7960
  content: ""
7722
7961
  });
7723
- jsonOk(
7724
- res,
7725
- buildEncryptedJsonReply({
7726
- account: target.account,
7727
- plaintextJson: reply,
7728
- nonce,
7729
- timestamp
7730
- })
7731
- );
7962
+ const encReply2 = buildEncryptedJsonReply({
7963
+ account: target.account,
7964
+ plaintextJson: reply,
7965
+ nonce,
7966
+ timestamp
7967
+ });
7968
+ jsonOk(res, encReply2);
7732
7969
  return true;
7733
7970
  }
7734
7971
  if (msgid && msgidToStreamId.has(msgid)) {
@@ -7876,15 +8113,13 @@ async function handleWecomWebhookRequest(req, res) {
7876
8113
  await waitForStreamContent(streamId, INITIAL_STREAM_WAIT_MS);
7877
8114
  const state = streams.get(streamId);
7878
8115
  const initialReply = state && (state.content.trim() || state.error) ? buildStreamReplyFromState(state) : buildStreamPlaceholderReply(streamId);
7879
- jsonOk(
7880
- res,
7881
- buildEncryptedJsonReply({
7882
- account: target.account,
7883
- plaintextJson: initialReply,
7884
- nonce,
7885
- timestamp
7886
- })
7887
- );
8116
+ const encReply = buildEncryptedJsonReply({
8117
+ account: target.account,
8118
+ plaintextJson: initialReply,
8119
+ nonce,
8120
+ timestamp
8121
+ });
8122
+ jsonOk(res, encReply);
7888
8123
  return true;
7889
8124
  }
7890
8125