feat(whatsapp): extract mentions from text and captions

WhatsApp's Baileys library requires a separate mentions array in the
message payload for @mentions to work properly. Without this array,
text like '@5511999999999' appears as plain text instead of a clickable
mention.

This change automatically extracts phone numbers in the format
@<10-15 digits> from the message text AND media captions, adding them
to the mentions array as JIDs (e.g., '5511999999999@s.whatsapp.net').

Supported message types:
- Text messages
- Image with caption
- Video with caption
- Document with caption

Fixes mentions in group chats when using the message tool.
This commit is contained in:
Zuri ✦ 2026-01-26 14:40:09 -03:00
parent 260f6e2c00
commit c3cb338937

View File

@ -19,6 +19,17 @@ export function createWebSendApi(params: {
sendOptions?: ActiveWebSendOptions, sendOptions?: ActiveWebSendOptions,
): Promise<{ messageId: string }> => { ): Promise<{ messageId: string }> => {
const jid = toWhatsappJid(to); const jid = toWhatsappJid(to);
const mentions: string[] = [];
if (text) {
const phoneRegex = /@(\d{10,15})/g;
let match: RegExpExecArray | null;
while ((match = phoneRegex.exec(text)) !== null) {
mentions.push(`${match[1]}@s.whatsapp.net`);
}
}
const mentionSpread = mentions.length > 0 ? { mentions } : {};
let payload: AnyMessageContent; let payload: AnyMessageContent;
if (mediaBuffer && mediaType) { if (mediaBuffer && mediaType) {
if (mediaType.startsWith("image/")) { if (mediaType.startsWith("image/")) {
@ -26,7 +37,8 @@ export function createWebSendApi(params: {
image: mediaBuffer, image: mediaBuffer,
caption: text || undefined, caption: text || undefined,
mimetype: mediaType, mimetype: mediaType,
}; ...mentionSpread,
} as AnyMessageContent;
} else if (mediaType.startsWith("audio/")) { } else if (mediaType.startsWith("audio/")) {
payload = { audio: mediaBuffer, ptt: true, mimetype: mediaType }; payload = { audio: mediaBuffer, ptt: true, mimetype: mediaType };
} else if (mediaType.startsWith("video/")) { } else if (mediaType.startsWith("video/")) {
@ -36,17 +48,19 @@ export function createWebSendApi(params: {
caption: text || undefined, caption: text || undefined,
mimetype: mediaType, mimetype: mediaType,
...(gifPlayback ? { gifPlayback: true } : {}), ...(gifPlayback ? { gifPlayback: true } : {}),
}; ...mentionSpread,
} as AnyMessageContent;
} else { } else {
payload = { payload = {
document: mediaBuffer, document: mediaBuffer,
fileName: "file", fileName: "file",
caption: text || undefined, caption: text || undefined,
mimetype: mediaType, mimetype: mediaType,
}; ...mentionSpread,
} as AnyMessageContent;
} }
} else { } else {
payload = { text }; payload = mentions.length > 0 ? ({ text, mentions } as AnyMessageContent) : { text };
} }
const result = await params.sock.sendMessage(jid, payload); const result = await params.sock.sendMessage(jid, payload);
const accountId = sendOptions?.accountId ?? params.defaultAccountId; const accountId = sendOptions?.accountId ?? params.defaultAccountId;