@xmanrui/dsh-im 4.38.0 → 4.38.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/PROACTIVE_DELIVERY.en.md +18 -2
- package/PROACTIVE_DELIVERY.md +18 -2
- package/README.en.md +2 -0
- package/README.md +2 -0
- package/lib/client.js +49 -5
- package/lib/index.js +320 -315
- package/locale/en.json +1 -1
- package/locale/zh.json +1 -1
- package/package.json +1 -1
- package/plugin-src/client/connection-error.js +12 -2
- package/plugin-src/host/channels/dingtalk/production.mjs +1 -1
- package/plugin-src/host/channels/feishu/production.mjs +1 -1
- package/plugin-src/host/channels/matrix/production.mjs +1 -1
- package/plugin-src/host/channels/qq/production.mjs +1 -1
- package/plugin-src/host/channels/shared/production.mjs +1 -1
- package/plugin-src/host/channels/slack/production.mjs +1 -1
- package/plugin-src/host/channels/wecom/production.mjs +1 -1
- package/plugin-src/host/channels/wecom-app/production.mjs +1 -1
- package/plugin-src/host/channels/weixin/production.mjs +1 -1
- package/plugin-src/host/channels/whatsapp/production.mjs +1 -1
- package/plugin-src/host/delivery-adapter.mjs +9 -1
- package/plugin-src/host/delivery-service.mjs +4 -3
- package/plugin-src/host/modern-harness-api.mjs +2 -1
- package/plugin-src/host/session-sync-coordinator.mjs +9 -5
- package/src/channels/dingtalk/dingtalk-api.mjs +41 -24
- package/src/channels/dingtalk/dingtalk-runtime.mjs +3 -2
- package/src/channels/discord/discord-runtime.mjs +13 -2
- package/src/channels/feishu/bridge.mjs +95 -24
- package/src/channels/matrix/matrix-runtime.mjs +16 -6
- package/src/channels/qq/markdown-reply.mjs +7 -340
- package/src/channels/qq/qq-runtime.mjs +11 -3
- package/src/channels/shared/bot-workspace-store.mjs +3 -3
- package/src/channels/shared/connection-error.mjs +29 -4
- package/src/channels/shared/diagnostic-details.mjs +6 -2
- package/src/channels/shared/harness-client.mjs +138 -19
- package/src/channels/shared/harness-mux.mjs +6 -4
- package/src/channels/shared/i18n-en/diagnostics.mjs +24 -0
- package/src/channels/shared/i18n-en/wecom.mjs +3 -0
- package/src/channels/shared/markdown-chunks.mjs +338 -0
- package/src/channels/shared/message-failure.mjs +31 -11
- package/src/channels/shared/text-harness-bridge.mjs +4 -1
- package/src/channels/shared/workspace-session.mjs +10 -1
- package/src/channels/slack/slack-api.mjs +2 -1
- package/src/channels/slack/slack-runtime.mjs +30 -0
- package/src/channels/telegram/telegram-runtime.mjs +1 -0
- package/src/channels/wecom/wecom-bridge.mjs +45 -10
- package/src/channels/wecom/wecom-runtime.mjs +11 -5
- package/src/channels/weixin/weixin-runtime.mjs +17 -10
- package/src/channels/whatsapp/whatsapp-markdown.mjs +59 -0
- package/src/channels/whatsapp/whatsapp-runtime.mjs +26 -1
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
isBatchInputCommand,
|
|
22
22
|
} from '../shared/batch-input.mjs';
|
|
23
23
|
import { runCompactCommand } from '../shared/compact-command.mjs';
|
|
24
|
+
import { isSharedLocalCommand } from '../shared/command-permission.mjs';
|
|
24
25
|
import { isClearCommand, runClearCommand } from '../shared/clear-command.mjs';
|
|
25
26
|
import { isHistoryCommand, runHistoryCommand } from '../shared/history-command.mjs';
|
|
26
27
|
import {
|
|
@@ -154,10 +155,9 @@ function messageText(frame) {
|
|
|
154
155
|
: text;
|
|
155
156
|
}
|
|
156
157
|
|
|
157
|
-
function imageContents(
|
|
158
|
-
|
|
159
|
-
if (body
|
|
160
|
-
if (body.msgtype !== 'mixed' || !Array.isArray(body.mixed?.msg_item)) return [];
|
|
158
|
+
function imageContents(body) {
|
|
159
|
+
if (body?.msgtype === 'image') return [body.image];
|
|
160
|
+
if (body?.msgtype !== 'mixed' || !Array.isArray(body.mixed?.msg_item)) return [];
|
|
161
161
|
return body.mixed.msg_item
|
|
162
162
|
.filter((item) => item?.msgtype === 'image')
|
|
163
163
|
.map((item) => item.image);
|
|
@@ -259,7 +259,7 @@ export function wecomInboundMessage(frame, client) {
|
|
|
259
259
|
const replyTo = replyReferenceForBody(body);
|
|
260
260
|
return {
|
|
261
261
|
content: messageText(frame),
|
|
262
|
-
images: imageContents(
|
|
262
|
+
images: imageContents(body).map((image) => imageSource(client, image)).filter(Boolean),
|
|
263
263
|
files: fileContents(frame).map((file) => fileSource(client, file)).filter(Boolean),
|
|
264
264
|
...(replyTo ? { replyTo } : {}),
|
|
265
265
|
};
|
|
@@ -337,6 +337,25 @@ function imageQueueFullMessage(message) {
|
|
|
337
337
|
};
|
|
338
338
|
}
|
|
339
339
|
|
|
340
|
+
function quotedImageGuidance(message) {
|
|
341
|
+
const lines = [];
|
|
342
|
+
const directCount = message.images.length;
|
|
343
|
+
const quotedCount = message.quotedImages?.length ?? 0;
|
|
344
|
+
if (quotedCount > 0) {
|
|
345
|
+
if (directCount > 0) lines.push(t('图片 {indexes} 来自当前消息。', {
|
|
346
|
+
indexes: Array.from({ length: directCount }, (_, index) => index + 1).join(', '),
|
|
347
|
+
}));
|
|
348
|
+
lines.push(t('图片 {indexes} 来自被引用消息。图片序号与本轮附件清单一致。', {
|
|
349
|
+
indexes: Array.from({ length: quotedCount }, (_, index) => directCount + index + 1).join(', '),
|
|
350
|
+
}));
|
|
351
|
+
}
|
|
352
|
+
if (message.missingQuotedImageCount > 0) lines.push(t(
|
|
353
|
+
'有 {count} 张引用图片未提供可下载资源,未附上这些图片;需要看图时请用户重新发送。',
|
|
354
|
+
{ count: message.missingQuotedImageCount },
|
|
355
|
+
));
|
|
356
|
+
return lines.join('\n');
|
|
357
|
+
}
|
|
358
|
+
|
|
340
359
|
function interactionReplyText(frame) {
|
|
341
360
|
return bodyOf(frame).msgtype === 'text' ? messageText(frame) : '';
|
|
342
361
|
}
|
|
@@ -1082,19 +1101,33 @@ export class WecomHarnessBridge {
|
|
|
1082
1101
|
// may legally stay queued longer. Start the authenticated SDK download as
|
|
1083
1102
|
// soon as the validated callback is accepted, then consume it in order.
|
|
1084
1103
|
const inboundMessage = wecomInboundMessage(frame, this.#client);
|
|
1085
|
-
const
|
|
1104
|
+
const directCount = inboundMessage.images.length;
|
|
1105
|
+
const quoteContents = isSharedLocalCommand(inboundMessage.content, {
|
|
1106
|
+
hasImages: hasInboundImages(inboundMessage),
|
|
1107
|
+
hasFiles: hasInboundFiles(inboundMessage),
|
|
1108
|
+
}) ? [] : imageContents(bodyOf(frame).quote);
|
|
1109
|
+
const quotedImages = quoteContents.map((image) => imageSource(this.#client, image)).filter(Boolean);
|
|
1110
|
+
const combinedMessage = { ...inboundMessage, images: [...inboundMessage.images, ...quotedImages] };
|
|
1111
|
+
const imageCount = combinedMessage.images.length;
|
|
1086
1112
|
let reservedImages = 0;
|
|
1087
|
-
let preparedMessage =
|
|
1113
|
+
let preparedMessage = combinedMessage;
|
|
1088
1114
|
if (imageCount > 0) {
|
|
1089
1115
|
if (this.#prefetchedImageCount + imageCount <= MAX_PREFETCHED_IMAGES) {
|
|
1090
1116
|
reservedImages = imageCount;
|
|
1091
1117
|
this.#prefetchedImageCount += reservedImages;
|
|
1092
|
-
preparedMessage = prefetchInboundImages(
|
|
1118
|
+
preparedMessage = prefetchInboundImages(combinedMessage, this.#signal,
|
|
1093
1119
|
this.#harness.getImageInputLimits?.() ?? imageInputLimits());
|
|
1094
1120
|
} else {
|
|
1095
|
-
preparedMessage = imageQueueFullMessage(
|
|
1121
|
+
preparedMessage = imageQueueFullMessage(combinedMessage);
|
|
1096
1122
|
}
|
|
1097
1123
|
}
|
|
1124
|
+
// Keep quoted media separate until local command and interaction routing is done.
|
|
1125
|
+
preparedMessage = {
|
|
1126
|
+
...preparedMessage,
|
|
1127
|
+
images: preparedMessage.images.slice(0, directCount),
|
|
1128
|
+
quotedImages: preparedMessage.images.slice(directCount),
|
|
1129
|
+
missingQuotedImageCount: quoteContents.length - quotedImages.length,
|
|
1130
|
+
};
|
|
1098
1131
|
preparedMessage = prefetchInboundFiles(preparedMessage, this.#signal);
|
|
1099
1132
|
const previous = this.#queues.get(key) ?? Promise.resolve();
|
|
1100
1133
|
const current = previous
|
|
@@ -1390,6 +1423,8 @@ export class WecomHarnessBridge {
|
|
|
1390
1423
|
let content = hasImages || hasReply
|
|
1391
1424
|
? await promptContentForInboundMessage(message, { signal: this.#signal, deferImages: true })
|
|
1392
1425
|
: undefined;
|
|
1426
|
+
const imageGuidance = quotedImageGuidance(message);
|
|
1427
|
+
if (imageGuidance) content.push({ type: 'text', text: imageGuidance });
|
|
1393
1428
|
const snapshot = this.#acceptedMessageIds.get(messageId);
|
|
1394
1429
|
let contextEnhanced = false;
|
|
1395
1430
|
if (snapshot) {
|
|
@@ -1440,7 +1475,7 @@ export class WecomHarnessBridge {
|
|
|
1440
1475
|
}),
|
|
1441
1476
|
onInteractionResolved: (resolution) => this.#handleInteractionResolved(resolution),
|
|
1442
1477
|
files: message.files,
|
|
1443
|
-
images: message.images,
|
|
1478
|
+
images: [...message.images, ...(message.quotedImages ?? [])],
|
|
1444
1479
|
},
|
|
1445
1480
|
});
|
|
1446
1481
|
if (batchSubmission) {
|
|
@@ -4,6 +4,7 @@ import { WSAuthFailureError, WSClient, WSReconnectExhaustedError } from '@wecom/
|
|
|
4
4
|
import { createWecomBridgeStatus, WecomHarnessBridge } from './wecom-bridge.mjs';
|
|
5
5
|
import { sendRememberedConnectionTest } from '../shared/connection-test.mjs';
|
|
6
6
|
import { t } from '../shared/i18n.mjs';
|
|
7
|
+
import { chunkMarkdownText } from '../shared/markdown-chunks.mjs';
|
|
7
8
|
|
|
8
9
|
function timeoutError() {
|
|
9
10
|
const error = new Error('Enterprise WeChat WebSocket authentication timed out');
|
|
@@ -240,7 +241,7 @@ export class WecomRuntime {
|
|
|
240
241
|
}, options);
|
|
241
242
|
}
|
|
242
243
|
|
|
243
|
-
async sendProactiveText(target, text, { signal } = {}) {
|
|
244
|
+
async sendProactiveText(target, text, { signal, format = 'plain' } = {}) {
|
|
244
245
|
const chatId = typeof target?.route?.chatId === 'string'
|
|
245
246
|
? target.route.chatId.trim() : '';
|
|
246
247
|
if ((target?.kind !== 'user' && target?.kind !== 'group') || !chatId) {
|
|
@@ -254,10 +255,15 @@ export class WecomRuntime {
|
|
|
254
255
|
throw error;
|
|
255
256
|
}
|
|
256
257
|
signal?.throwIfAborted();
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
258
|
+
// At most three UTF-8 bytes per UTF-16 unit: stay within the existing
|
|
259
|
+
// bridge's 18,000-byte budget while keeping Markdown structures intact.
|
|
260
|
+
const chunks = format === 'markdown' ? chunkMarkdownText(text, 6_000) : [text];
|
|
261
|
+
for (const content of chunks) {
|
|
262
|
+
signal?.throwIfAborted();
|
|
263
|
+
await this.#client.sendMessage(chatId, {
|
|
264
|
+
msgtype: 'markdown', markdown: { content },
|
|
265
|
+
});
|
|
266
|
+
}
|
|
261
267
|
return { sent: true };
|
|
262
268
|
}
|
|
263
269
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createWeixinDiagnostics } from './connection-error.mjs';
|
|
2
|
-
import { DEFAULT_WEIXIN_MAX_MESSAGE_CHARS, WeixinApiError, rejectedProviderResponse } from './weixin-api.mjs';
|
|
2
|
+
import { DEFAULT_WEIXIN_MAX_MESSAGE_CHARS, WeixinApiError, rejectedProviderResponse, splitWeixinText } from './weixin-api.mjs';
|
|
3
|
+
import { chunkMarkdownText } from '../shared/markdown-chunks.mjs';
|
|
3
4
|
import {
|
|
4
5
|
createWeixinBridgeStatus, WeixinHarnessBridge, weixinSendError, weixinSendFailureOptions,
|
|
5
6
|
} from './weixin-bridge.mjs';
|
|
@@ -350,7 +351,7 @@ export class WeixinRuntime {
|
|
|
350
351
|
return { sent: true };
|
|
351
352
|
}
|
|
352
353
|
|
|
353
|
-
async #sendTrackedText({ toUserId, text, signal }) {
|
|
354
|
+
async #sendTrackedText({ toUserId, text, signal, fullText = text, chunkIndex = 0, chunkCount = 1 }) {
|
|
354
355
|
const sentAt = Date.now();
|
|
355
356
|
const contextToken = this.#state.contextTokenFor?.(toUserId);
|
|
356
357
|
let result;
|
|
@@ -366,7 +367,7 @@ export class WeixinRuntime {
|
|
|
366
367
|
} catch (cause) {
|
|
367
368
|
if (signal?.aborted) throw cause;
|
|
368
369
|
const error = weixinSendError(cause, {
|
|
369
|
-
baseUrl: this.#config.baseUrl, text, chunk: text, chunkIndex
|
|
370
|
+
baseUrl: this.#config.baseUrl, text: fullText, chunk: text, chunkIndex, chunkCount,
|
|
370
371
|
maxMessageChars: this.#maxMessageChars, contextToken,
|
|
371
372
|
});
|
|
372
373
|
const failure = setLastMessageFailure(this.#status,
|
|
@@ -402,7 +403,7 @@ export class WeixinRuntime {
|
|
|
402
403
|
}, options);
|
|
403
404
|
}
|
|
404
405
|
|
|
405
|
-
async sendProactiveText(target, text, { signal } = {}) {
|
|
406
|
+
async sendProactiveText(target, text, { signal, format = 'plain' } = {}) {
|
|
406
407
|
const toUserId = typeof target?.route?.toUserId === 'string'
|
|
407
408
|
? target.route.toUserId.trim() : '';
|
|
408
409
|
if (target?.kind !== 'user' || !toUserId) {
|
|
@@ -415,12 +416,18 @@ export class WeixinRuntime {
|
|
|
415
416
|
error.code = 'bot-not-connected';
|
|
416
417
|
throw error;
|
|
417
418
|
}
|
|
418
|
-
signal
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
text,
|
|
422
|
-
|
|
423
|
-
|
|
419
|
+
const sendSignal = signal ?? this.#abortController.signal;
|
|
420
|
+
sendSignal.throwIfAborted();
|
|
421
|
+
const chunks = format === 'markdown'
|
|
422
|
+
? chunkMarkdownText(text, this.#maxMessageChars)
|
|
423
|
+
: splitWeixinText(text, this.#maxMessageChars);
|
|
424
|
+
for (const [chunkIndex, chunk] of chunks.entries()) {
|
|
425
|
+
sendSignal.throwIfAborted();
|
|
426
|
+
await this.#sendTrackedText({
|
|
427
|
+
toUserId, text: chunk, signal: sendSignal,
|
|
428
|
+
fullText: text, chunkIndex, chunkCount: chunks.length,
|
|
429
|
+
});
|
|
430
|
+
}
|
|
424
431
|
return { sent: true };
|
|
425
432
|
}
|
|
426
433
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// A conservative projection of common Markdown into WhatsApp's text dialect.
|
|
2
|
+
// Unsupported structures stay literal; code and link destinations are protected.
|
|
3
|
+
function inline(text) {
|
|
4
|
+
const saved = [];
|
|
5
|
+
let marker = '\u0000';
|
|
6
|
+
while (text.includes(marker)) marker += '\u0000';
|
|
7
|
+
const stash = value => `${marker}${saved.push(value) - 1}${marker}`;
|
|
8
|
+
let value = text.replace(/(`+)([^\n]*?)\1/g, (_match, ticks, code) => (
|
|
9
|
+
// WhatsApp cannot represent embedded backticks in inline code safely.
|
|
10
|
+
stash(code.includes('`') ? `${ticks}${code}${ticks}` : `\`${code}\``)
|
|
11
|
+
));
|
|
12
|
+
value = value.replace(/\\([\\`*_{}[\]()#+.!~>-])/g, match => stash(match));
|
|
13
|
+
value = value.replace(/!?\[([^\]\n]+)\]\((https?:\/\/(?:[^\s()]|\([^\s()]*\))+)(?:\s+"[^"\n]*")?\)/g,
|
|
14
|
+
(_match, label, url) => stash(`${label} (${url})`));
|
|
15
|
+
value = value.replace(/https?:\/\/[^\s<>]+/g, url => stash(url));
|
|
16
|
+
// Leave nested delimiters untouched instead of guessing their meaning.
|
|
17
|
+
value = value.replace(/(?<![*_])(\*\*|__)([^*_\n]+)\1(?![*_])/g,
|
|
18
|
+
(_match, _marker, body) => stash(`*${body}*`));
|
|
19
|
+
value = value.replace(/(?<![\w*_])\*([^*\n]+)\*(?![*_])/g, (_match, body) => `_${body}_`);
|
|
20
|
+
value = value.replace(/(?<!~)~~([^~\n]+)~~(?!~)/g, '~$1~');
|
|
21
|
+
const token = new RegExp(`${marker}(\\d+)${marker}`, 'g');
|
|
22
|
+
// A later protected URL can contain an earlier token; restore both passes.
|
|
23
|
+
for (let pass = 0; pass <= saved.length && value.includes(marker); pass++) {
|
|
24
|
+
value = value.replace(token, (match, index) => saved[Number(index)] ?? match);
|
|
25
|
+
}
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function markdownToWhatsapp(text) {
|
|
30
|
+
const lines = text.replace(/\r\n?/g, '\n').split('\n');
|
|
31
|
+
const result = [];
|
|
32
|
+
let table = false;
|
|
33
|
+
for (let index = 0; index < lines.length; index++) {
|
|
34
|
+
const line = lines[index];
|
|
35
|
+
const open = /^ {0,3}(`{3,}|~{3,})(.*)$/.exec(line);
|
|
36
|
+
if (open && !(open[1][0] === '`' && open[2].includes('`'))) {
|
|
37
|
+
let end = index + 1;
|
|
38
|
+
for (; end < lines.length; end++) {
|
|
39
|
+
const close = /^ {0,3}(`{3,}|~{3,})[ \t]*$/.exec(lines[end]);
|
|
40
|
+
if (close && close[1][0] === open[1][0] && close[1].length >= open[1].length) break;
|
|
41
|
+
}
|
|
42
|
+
const body = lines.slice(index + 1, end);
|
|
43
|
+
// Embedded triple backticks cannot be represented by WhatsApp's fence.
|
|
44
|
+
// Preserve the entire original block instead of changing code boundaries.
|
|
45
|
+
if (body.some(value => value.includes('```'))) result.push(...lines.slice(index, end + 1));
|
|
46
|
+
else result.push('```', ...body, '```');
|
|
47
|
+
index = end;
|
|
48
|
+
table = false;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (line.includes('|') && /^\s*\|?\s*:?-+:?\s*\|[\s|:-]*$/.test(lines[index + 1] ?? '')) table = true;
|
|
52
|
+
if (table && line.includes('|')) { result.push(line); continue; }
|
|
53
|
+
table = false;
|
|
54
|
+
const heading = /^ {0,3}#{1,6}\s+(.+?)(?:\s+#+)?\s*$/.exec(line);
|
|
55
|
+
// A heading with its own inline delimiters stays literal rather than nested.
|
|
56
|
+
result.push(heading && !/[*_`]/.test(heading[1]) ? `*${heading[1]}*` : inline(line));
|
|
57
|
+
}
|
|
58
|
+
return result.join('\n');
|
|
59
|
+
}
|
|
@@ -14,6 +14,9 @@ import { t } from '../shared/i18n.mjs';
|
|
|
14
14
|
import { ImagePromptError } from '../shared/image-prompt.mjs';
|
|
15
15
|
import { trackOutboundArtifactProviderPromise } from '../shared/semantic/artifact.mjs';
|
|
16
16
|
import { createWhatsappBridgeStatus, WhatsappHarnessBridge } from './whatsapp-bridge.mjs';
|
|
17
|
+
import { createTextDeliveryBlock } from '../shared/semantic/delivery.mjs';
|
|
18
|
+
import { chunkMarkdownText } from '../shared/markdown-chunks.mjs';
|
|
19
|
+
import { markdownToWhatsapp } from './whatsapp-markdown.mjs';
|
|
17
20
|
import {
|
|
18
21
|
WHATSAPP_ACCESS_MODES,
|
|
19
22
|
normalizeWhatsappAccountJid,
|
|
@@ -490,6 +493,25 @@ export class WhatsappBotClient {
|
|
|
490
493
|
return { providerMessageIds };
|
|
491
494
|
}
|
|
492
495
|
|
|
496
|
+
async sendDelivery(target, value) {
|
|
497
|
+
const block = createTextDeliveryBlock(value);
|
|
498
|
+
if (block.format === 'plain') return this.sendText(target, block.text);
|
|
499
|
+
let chunks;
|
|
500
|
+
try {
|
|
501
|
+
chunks = chunkMarkdownText(markdownToWhatsapp(block.text), WHATSAPP_TEXT_LIMIT);
|
|
502
|
+
} catch {
|
|
503
|
+
this.#logger.warn?.('[dsh-im:whatsapp] Markdown could not be prepared; sending plain text');
|
|
504
|
+
return this.sendText(target, block.text);
|
|
505
|
+
}
|
|
506
|
+
await this.#stopTyping(target.jid);
|
|
507
|
+
const providerMessageIds = [];
|
|
508
|
+
for (const [index, chunk] of chunks.entries()) {
|
|
509
|
+
const result = await this.#sendTextMessage(target, chunk, { quote: index === 0 });
|
|
510
|
+
providerMessageIds.push(result.key.id);
|
|
511
|
+
}
|
|
512
|
+
return { providerMessageIds };
|
|
513
|
+
}
|
|
514
|
+
|
|
493
515
|
async #sendTextMessage(target, text, { edit, quote = true } = {}) {
|
|
494
516
|
this.#signal?.throwIfAborted();
|
|
495
517
|
const messageId = randomBytes(10).toString('hex').toUpperCase();
|
|
@@ -886,7 +908,7 @@ export class WhatsappRuntime {
|
|
|
886
908
|
}, options);
|
|
887
909
|
}
|
|
888
910
|
|
|
889
|
-
async sendProactiveText(target, text, { signal } = {}) {
|
|
911
|
+
async sendProactiveText(target, text, { signal, format = 'plain' } = {}) {
|
|
890
912
|
const jid = typeof target?.route?.jid === 'string' ? target.route.jid.trim() : '';
|
|
891
913
|
const validUser = target?.kind === 'user'
|
|
892
914
|
&& /^[^@\s]+@(s\.whatsapp\.net|lid)$/.test(jid);
|
|
@@ -902,6 +924,9 @@ export class WhatsappRuntime {
|
|
|
902
924
|
throw error;
|
|
903
925
|
}
|
|
904
926
|
signal?.throwIfAborted();
|
|
927
|
+
if (format === 'markdown') {
|
|
928
|
+
return this.#client.sendDelivery({ jid }, createTextDeliveryBlock(text, format));
|
|
929
|
+
}
|
|
905
930
|
return this.#client.sendText({ jid }, text);
|
|
906
931
|
}
|
|
907
932
|
|