Saltar al contenido principal

Cómo Reenviar Mensajes de WhatsApp Programáticamente Cómo Reenviar Mensajes de WhatsApp Programáticamente

Cómo Reenviar Mensajes de WhatsApp Programáticamente

Reenvía mensajes entre chats de WhatsApp usando sendRawMessage(). Puedes reenviar texto, multimedia, encuestas y cualquier otro tipo de mensaje — con o sin la etiqueta "Reenviado".

Requisitos Previos

Reenviar un Mensaje de Texto

Para reenviar un mensaje recibido a otro chat, pásalo vía sendRawMessage con el flag isForwarded:

const targetJid = "5598765432@s.whatsapp.net";

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;

// Forward as a new message with the "Forwarded" label
await client.sendRawMessage(targetJid, {
extendedTextMessage: {
text,
contextInfo: {
isForwarded: true,
forwardingScore: 1,
},
},
});
});

El flag isForwarded: true muestra la etiqueta "Reenviado" en el mensaje. forwardingScore rastrea cuántas veces se ha reenviado el mensaje — WhatsApp muestra "Reenviado muchas veces" cuando llega a 4+.

Reenviar Sin la Etiqueta "Reenviado"

Para retransmitir un mensaje sin la etiqueta de reenvío, simplemente envíalo como mensaje nuevo:

// Send as if it's a new message — no forwarding label
await client.sendMessage(targetJid, { conversation: text });

Reenviar Mensajes Multimedia

Los mensajes multimedia (imágenes, videos, documentos) se pueden reenviar pasando el contenido del mensaje original. La multimedia ya está subida a los servidores de WhatsApp, así que no se necesita re-subir:

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

// Forward images
if (message.imageMessage) {
await client.sendRawMessage(targetJid, {
imageMessage: {
...message.imageMessage,
contextInfo: {
isForwarded: true,
forwardingScore: 1,
},
},
});
return;
}

// Forward videos
if (message.videoMessage) {
await client.sendRawMessage(targetJid, {
videoMessage: {
...message.videoMessage,
contextInfo: {
isForwarded: true,
forwardingScore: 1,
},
},
});
return;
}

// Forward documents
if (message.documentMessage) {
await client.sendRawMessage(targetJid, {
documentMessage: {
...message.documentMessage,
contextInfo: {
isForwarded: true,
forwardingScore: 1,
},
},
});
return;
}
});

Reenviar Cualquier Tipo de Mensaje

Una función genérica que reenvía cualquier mensaje detectando su tipo:

const MESSAGE_TYPES = [
"conversation",
"extendedTextMessage",
"imageMessage",
"videoMessage",
"audioMessage",
"documentMessage",
"stickerMessage",
"contactMessage",
"locationMessage",
"pollCreationMessage",
] as const;

async function forwardMessage(
targetJid: string,
message: Record<string, unknown>,
showForwarded = true,
) {
for (const type of MESSAGE_TYPES) {
if (!(type in message)) continue;

if (type === "conversation") {
// Simple text — wrap in extendedTextMessage for contextInfo support
await client.sendRawMessage(targetJid, {
extendedTextMessage: {
text: message.conversation as string,
...(showForwarded && {
contextInfo: { isForwarded: true, forwardingScore: 1 },
}),
},
});
} else {
// Structured message — spread and add contextInfo
const content = message[type] as Record<string, unknown>;
await client.sendRawMessage(targetJid, {
[type]: {
...content,
...(showForwarded && {
contextInfo: {
...(content.contextInfo as Record<string, unknown> ?? {}),
isForwarded: true,
forwardingScore: 1,
},
}),
},
});
}
return;
}
}

Crear un Bot Relay

Reenvía todos los mensajes de un grupo a otro:

const SOURCE_GROUP = "120363XXXXX@g.us";
const TARGET_GROUP = "120363YYYYY@g.us";

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

// Add sender attribution
const attribution = `[${info.pushName}]:\n`;

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

if (text) {
await client.sendMessage(TARGET_GROUP, {
conversation: `${attribution}${text}`,
});
return;
}

// Forward media with caption attribution
await forwardMessage(TARGET_GROUP, message);
});

Ejemplo Completo

Un bot relay que reenvía mensajes entre un chat privado y un grupo:

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

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

// Forward messages FROM this chat TO the group
const PRIVATE_CHAT = "5512345678@s.whatsapp.net";
const GROUP_CHAT = "120363XXXXX@g.us";

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

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

// Private → Group
if (info.chat === PRIVATE_CHAT && text) {
await client.sendMessage(GROUP_CHAT, {
conversation: `[${info.pushName}]: ${text}`,
});
return;
}

// Group → Private
if (info.chat === GROUP_CHAT && text) {
await client.sendMessage(PRIVATE_CHAT, {
conversation: `[${info.pushName} in group]: ${text}`,
});
return;
}
});

async function main() {
const { jid } = await client.init();
if (!jid) {
console.error("Not paired!");
process.exit(1);
}
await client.connect();
await client.sendPresence("available");
console.log("Relay bot is online!");

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

main().catch(console.error);

Errores Comunes

Bucles de eco

Si reenvías mensajes de un chat y también escuchas el chat destino, puedes crear un bucle infinito. Siempre verifica el chat de origen y omite tus propios mensajes.

Expiración de multimedia

Las URLs de multimedia de WhatsApp expiran después de un tiempo. Si guardas un mensaje e intentas reenviarlo mucho después, la multimedia puede ya no estar disponible. Reenvía la multimedia rápidamente o descárgala primero con downloadAny().

Límites de tasa

Reenviar muchos mensajes rápidamente va a alcanzar los límites de tasa de WhatsApp. Espacia los envíos, especialmente para operaciones de relay masivo. Consulta Límites de Tasa.