Saltar al contenido principal

Cómo Enviar Mensajes de WhatsApp desde TypeScript Cómo Enviar Mensajes de WhatsApp desde TypeScript

Cómo Enviar Mensajes de WhatsApp desde TypeScript

whatsmeow-node te da métodos async tipados para cada tipo de mensaje de WhatsApp — texto, respuestas, menciones, multimedia, encuestas y reacciones. Esta guía cubre cada uno con ejemplos en TypeScript.

Requisitos Previos

Configurar el Cliente

import { createClient } from "@whatsmeow-node/whatsmeow-node";

const client = createClient({ store: "session.db" });

async function main() {
const { jid: myJid } = await client.init();
if (!myJid) {
console.error("Not paired — run the pairing flow first");
process.exit(1);
}
await client.connect();
await client.waitForConnection();

const recipient = "5512345678@s.whatsapp.net";

// ... send messages here
}

main().catch(console.error);

Enviar un Mensaje de Texto

La forma más simple — pasa un string conversation:

const resp = await client.sendMessage(recipient, {
conversation: "Hello from TypeScript!",
});

console.log(`Sent with ID: ${resp.id}`);

sendMessage es el método de alto nivel. Recibe un JID y un objeto MessageContent, y devuelve un SendResponse con el id y timestamp del mensaje.

Responder con Cita

Para mostrar el mensaje original en una burbuja de cita, usa sendRawMessage con contextInfo:

// Assume we received a message and have its info
client.on("message", async ({ info, message }) => {
if (info.isFromMe) return;

const text =
(message.conversation as string) ??
(message.extendedTextMessage as { text?: string } | undefined)?.text;
if (!text) return;

await client.sendRawMessage(info.chat, {
extendedTextMessage: {
text: `You said: "${text}"`,
contextInfo: {
stanzaId: info.id,
participant: info.sender,
quotedMessage: { conversation: text },
},
},
});
});

@Mencionar Usuarios

Incluye los JID en mentionedJid y usa @<número> en el texto:

await client.sendRawMessage(groupJid, {
extendedTextMessage: {
text: `Hey @${memberJid.split("@")[0]}, check this out!`,
contextInfo: {
mentionedJid: [memberJid],
},
},
});

Para mencionar a todos en un grupo:

const group = await client.getGroupInfo(groupJid);
const jids = group.participants.map((p) => p.jid);
const mentions = jids.map((j) => `@${j.split("@")[0]}`).join(" ");

await client.sendRawMessage(groupJid, {
extendedTextMessage: {
text: `Attention: ${mentions}`,
contextInfo: { mentionedJid: jids },
},
});

Enviar Multimedia

Primero sube el archivo, luego envía los metadatos con un mensaje raw:

// Upload an image
const media = await client.uploadMedia("/path/to/photo.jpg", "image");

await client.sendRawMessage(recipient, {
imageMessage: {
URL: media.URL,
directPath: media.directPath,
mediaKey: media.mediaKey,
fileEncSHA256: media.fileEncSHA256,
fileSHA256: media.fileSHA256,
fileLength: String(media.fileLength),
mimetype: "image/jpeg",
caption: "Check this out!",
},
});

Otros tipos de multimedia siguen el mismo patrón — videoMessage, audioMessage, documentMessage, stickerMessage.

Casing de campos proto

Los campos de respuesta de upload usan el casing exacto de protobuf: URL, fileSHA256, fileEncSHA256no url, fileSha256. Un casing incorrecto falla silenciosamente.

Enviar una Encuesta

const resp = await client.sendPollCreation(
groupJid,
"Where should we eat?", // question
["Pizza", "Sushi", "Tacos"], // options
1, // max selectable
);

Reaccionar a un Mensaje

// Add a reaction
await client.sendReaction(chat, senderJid, messageId, "🔥");

// Remove a reaction (empty string)
await client.sendReaction(chat, senderJid, messageId, "");

Editar un Mensaje Enviado

const sent = await client.sendMessage(recipient, {
conversation: "Hello!",
});

// Edit it — only works on your own messages
await client.editMessage(recipient, sent.id, {
conversation: "Hello! (edited)",
});

Eliminar un Mensaje

// Revoke for everyone — only your own messages, within the time limit
await client.revokeMessage(chat, myJid, sent.id);

Marcar como Leído

await client.markRead([info.id], info.chat, info.sender);

Ejemplo Completo

Un bot que repite los mensajes de texto como respuestas citadas:

import { createClient } from "@whatsmeow-node/whatsmeow-node";
import qrcode from "qrcode-terminal";

const client = createClient({ store: "session.db" });

client.on("error", (err) => console.error("Error:", err));

client.on("message", async ({ info, message }) => {
if (info.isFromMe) return;

const text =
(message.conversation as string) ??
(message.extendedTextMessage as { text?: string } | undefined)?.text;
if (!text) return;

await client.markRead([info.id], info.chat, info.sender);
await client.sendChatPresence(info.chat, "composing");

await client.sendRawMessage(info.chat, {
extendedTextMessage: {
text: `Echo: ${text}`,
contextInfo: {
stanzaId: info.id,
participant: info.sender,
quotedMessage: { conversation: text },
},
},
});
});

async function main() {
const { jid } = await client.init();
if (!jid) {
client.on("qr", ({ code }) => qrcode.generate(code, { small: true }));
await client.getQRChannel();
}
await client.connect();
console.log("Listening for messages...");

process.on("SIGINT", async () => {
await client.sendPresence("unavailable");
await client.disconnect();
client.close();
process.exit(0);
});
}

main().catch(console.error);

Errores Comunes

sendMessage vs sendRawMessage

sendMessage es para texto simple. Para cualquier cosa estructurada (citas, menciones, multimedia), usa sendRawMessage con la forma completa del protobuf.

Los mensajes de grupo van a info.chat

En grupos, siempre envía a info.chat (el JID del grupo), no a info.sender (el individuo). Enviar a info.sender inicia una conversación privada.

Límites de tasa

WhatsApp limita la tasa de envío. Espacia los mensajes, especialmente en grupos. Consulta Límites de Tasa para más detalles.