Parte 1: Si no tiene tiempo para leer
- Responder a un comando desconocido, esto podría ser una instrucción o tu mensaje de bienvenida;
- Enviar mensaje regular;
- Enviar imagen;
- Enviar archivo;
- Enviar video;
- Enviar contacto (vCard);
- Enviar producto;
- Crear nuevo grupo, enviar una invitación y enviar mensaje al grupo;
- Recibir y leer mensajes entrantes;
Parte 2: Introducción
¿Qué es un bot de WhatsApp y por qué lo necesitas?
¿Por qué Node.js?
- Programación asíncrona. Node.js es no bloqueante, lo que significa que puede manejar múltiples conexiones simultáneamente, perfecto para un chatbot que necesita gestionar múltiples interacciones a la vez.
- Amplio soporte de la comunidad. Con un ecosistema vibrante y una gran cantidad de módulos gratuitamente disponibles en el registro npm, nunca te sentirás realmente solo programando en Node.js.
- Facilidad de aprendizaje. Si ya estás familiarizado con JavaScript, aprender Node.js será pan comido. Incluso si no lo estás, JavaScript es uno de los lenguajes de programación más fáciles de aprender.
- Nuestro soporte. Estamos encantados de ayudar a los desarrolladores en este lenguaje de programación. Si nos envías un fragmento de tu código donde algo no funciona, será más fácil para nosotros ayudarte, darte una pista o mejorar tu trabajo.
Configuración del entorno de desarrollo
- Node.js y npm. Si no tienes Node.js y npm (Node Package Manager) instalados en tu computadora, necesitarás instalarlos. Puedes descargar la última versión desde el sitio web oficial de Node.js.
- Editor de texto. Cualquier editor de texto servirá, pero IDEs como Visual Studio Code o WebStorm ofrecen características adicionales como depuración y autocompletado que pueden facilitar tu vida.
- Terminal o Símbolo del sistema. Aquí es donde ejecutarás todos tus scripts de Node.js.
Pasos para la instalación
- Paso 1: Instala Node.js y npm. Visita el sitio web oficial de Node.js y descarga el instalador para tu sistema operativo. Ejecuta el instalador y sigue las instrucciones en pantalla. Esto instalará tanto Node.js como npm.
- Paso 2: Configura la carpeta de tu proyecto. Crea una nueva carpeta donde almacenarás todos los archivos relacionados con tu bot de WhatsApp. Navega a la carpeta usando la terminal. Inicializa un nuevo proyecto de Node.js. Esto creará un archivo package.json que contendrá todos los metadatos y dependencias de tu proyecto.
- Paso 3: Instala los paquetes necesarios. Dado que trabajaremos con la API de WhatsApp, necesitaremos instalar algunos paquetes para ayudarnos con la tarea.
Conectando con el proveedor de la API de WhatsApp
Cómo conectar con Whapi.Cloud
Estableciendo los fundamentos del bot de WhatsApp
Inicializando el servidor con Express
npm install express --save
const express = require('express');
const app = express();
const port = 3000;
app.listen(port, () => {
console.log(`Server running on http://localhost:${port}/`);
});
Conceptos básicos de programación asíncrona en Node.js
async function fetchData() {
const data = await getData();
console.log(data);
}
Manejando mensajes entrantes
Hemos explorado todos los matices del trabajo con webhooks en más detalle en nuestra base de conocimientos: Ver artículo sobre webhooks
Pasemos a configurar esta URL!
Local o en el servidor
Ejecuta ./ngrok http NÚMERO_DE_PUERTO, reemplazando NÚMERO_DE_PUERTO con el puerto en el que tu servidor Express está funcionando localmente.
Ahora deberías tener una URL pública, que puedes usar como URL para el webhook.
Copia esta URL en el archivo config.js:
botUrl: "https://84c7-151-33-282-113.ngrok-free.app/hook"
Enrutamiento y manejo de solicitudes
const express = require('express');
const app = express();
app.use(express.json());
app.post('/webhook', (req, res) => {
// Handle incoming messages
});
app.listen(3000, () => {
console.log('Server started');
});
app.post('/webhook', async (req, res) => {
const message = req.body.message.text;
if (message === 'hello') {
await sendMessage('Hi there!');
} else if (message === 'help') {
await sendMessage('How can I assist you today?');
}
res.sendStatus(200);
});
Enviar mensajes de WhatsApp usando Node JS
Enviando mensajes de texto
const request = require('request');
const options = {
method: 'POST',
url: 'https://gate.whapi.cloud/messages/text?token=YOUR_API_TOKEN',
headers: {accept: 'application/json', 'content-type': 'application/json'},
body: {typing_time: 10, to: '[email protected]', body: 'Hello, world!'},
json: true
};
request(options, function (error, response, body) {
if (error) throw new Error(error);
console.log(body);
});
Enviando multimedia, documentos
const request = require('request');
const options = {
method: 'POST',
url: 'https://gate.whapi.cloud/messages/document?token=YOUR_API_TOKEN',
headers: {accept: 'application/json', 'content-type': 'application/json'},
body: {
to: '[email protected]',
media: 'data:application/octet-stream;name=site.webmanifest;base64,ewogICAgIm5hbWUiOiAiIiwKICAgICJzaG9ydF9uYW1lIjogIiIsCiAgICAiaWNvbnMiOiBbCiAgICAgICAgewogICAgICAgICAgICAic3JjIjogIi9hbmRyb2lkLWNocm9tZS0xOTJ4MTkyLnBuZyIsCiAgICAgICAgICAgICJzaXplcyI6ICIxOTJ4MTkyIiwKICAgICAgICAgICAgInR5cGUiOiAiaW1hZ2UvcG5nIgogICAgICAgIH0KICAgIF0sCiAgICAidGhlbWVfY29sb3IiOiAiI2ZmZmZmZiIsCiAgICAiYmFja2dyb3VuZF9jb2xvciI6ICIjZmZmZmZmIiwKICAgICJkaXNwbGF5IjogInN0YW5kYWxvbmUiCn0K'
},
json: true
};
request(options, function (error, response, body) {
if (error) throw new Error(error);
console.log(body);
});
Trabajando con grupos de WhatsApp usando Node.JS
Obteniendo la lista de grupos
const request = require('request');
const options = {
method: 'GET',
url: 'https://gate.whapi.cloud/groups?token=YOUR_API_TOKEN',
headers: {accept: 'application/json'}
};
request(options, function (error, response, body) {
if (error) throw new Error(error);
console.log(body);
});
Contando el número de participantes en el grupo de WhatsApp
request(options, function (error, response, body) {
if (error) throw new Error(error);
const groups = JSON.parse(body);
groups.forEach(group => {
console.log(`Group Name: ${group.name}`);
console.log(`Number of Participants: ${group.participants.length}`);
});
});
Manejando mensajes de grupo
app.post('/webhook', (req, res) => {
const message = req.body;
if (message.from.endsWith('@g.us')) {
// This is a group message
handleGroupMessage(message);
} else if (message.type === 'text') {
// Handle Text Messages
sendTextMessage(message.from, 'You said: ' + message.body);
}
res.status(200).end();
});
function handleGroupMessage(message) {
console.log(`Received a group message from ${message.from}: ${message.body}`);
// Further logic for group messages can be implemented here.
}
Solución de problemas
El bot no responde a mensajes entrantes
- Asegúrate de que estás enviando mensajes al número en el que se ejecuta el bot desde otro teléfono. El bot no podrá reaccionar a los mensajes enviados desde el mismo número.
- Si el bot no reacciona a los mensajes de otros números, verifica el funcionamiento de los webhooks. Utiliza servicios para simular webhooks, por ejemplo, Webhook.site, para asegurarte de por dónde llegan las solicitudes de callback. Luego, verifica que el camino coincide con lo que configuraste. Además, asegúrate de que tu servidor responde con 200Ok.
El bot envía mensajes sin parar
El bot funciona en algunos chats, pero en otros no
Despliegue y uso de servidores
Firebase
- Crea un proyecto en Firebase Console;
- Instala Firebase CLI, siguiendo las instrucciones;
- Inicializa Firebase en el directorio de tu proyecto con el comando firebase init;
- Despliega tu bot usando el comando firebase deploy --only functions.
AWS (Amazon Web Services)
- Regístrate o inicia sesión en AWS Management Console;
- Crea una nueva función Lambda a través de la consola de AWS, eligiendo API Gateway como desencadenante;
- Sube el código de tu bot a la función Lambda;
- Configura API Gateway para que tu bot interactúe con el mundo exterior.
Heroku
- Crea una cuenta en Heroku;
- Instala Heroku CLI e inicia sesión;
- Crea una nueva aplicación en Heroku a través de la consola o usando el comando heroku create;
- Conecta tu repositorio Git con Heroku y realiza el despliegue con los comandos git push heroku master;
- Configura la URL del webhook proporcionada por Heroku.
Código fuente de un bot de WhatsApp simple listo para usar en Node.JS
module.exports = {
// API endpoint URL
apiUrl: "https://gate.whapi.cloud",
// API token from your channel
token: "YOUR CHANNEL TOKEN",
// The ID of the group to which we will send the message. Use to find out the ID: https://whapi.readme.io/reference/getgroups
group: '[email protected]',
// The ID of the product we will send for the example. Create a product in your WhatsApp and find out the product ID: https://whapi.readme.io/reference/getproducts
product: '6559353560856703',
// Bot`s URL (for static file). Webhook Link to your server. At ( {server link}/hook ), when POST is requested, processing occurs
botUrl: "https://yoursite.com/hook",
// Bot's Port (for hook handler). Don't use 443 port.
port: "80"
}
const express = require('express');
const bodyParser = require('body-parser');
const fs = require('fs');
const fetch = require('node-fetch');
const FormData = require('form-data');
const config = require('./config.js');
process.on('unhandledRejection', err => {
console.log(err)
});
const COMMANDS = { // bot commands
TEXT: 'Simple text message',
IMAGE: 'Send image',
DOCUMENT: 'Send document',
VIDEO: 'Send video',
CONTACT: 'Send contact',
PRODUCT: 'Send product',
GROUP_CREATE: 'Create group',
GROUP_TEXT: 'Simple text message for the group',
GROUPS_IDS: 'Get the id\'s of your three groups'
}
const FILES = { // file path
IMAGE: './files/file_example_JPG_100kB.jpg',
DOCUMENT: './files/file-example_PDF_500_kB.pdf',
VIDEO: './files/file_example_MP4_480_1_5MG.mp4',
VCARD: './files/sample-vcard.txt'
}
async function sendWhapiRequest(endpoint, params= {}, method = 'POST') { // send request to endpoint with params, with POST by default
let options = {
method: method,
headers: {
Authorization: `Bearer ${config.token}`
},
};
if (!params.media) options.headers['Content-Type'] = 'application/json'; // if in params media is null - set json in headers
let url = `${config.apiUrl}/${endpoint}`;
if(params && Object.keys(params).length > 0) {
if(method === 'GET')
url += '?' + new URLSearchParams(params); // if GET method set in params, then params move to query
else
options.body = params?.media ? toFormData(params) : JSON.stringify(params); // if in params media - transform to formData, else - json
}
const response = await fetch(url, options); // send request
let json = await response.json();
console.log('Whapi response:', JSON.stringify(json, null, 2));
return json;
}
/**
* Convert object to FormData
* @param obj
* @returns {FormData}
*/
function toFormData(obj) {
const form = new FormData();
for (let key in obj) {
form.append(key, obj[key]);
}
return form;
}
async function setHook() { // request for set hook and recieve messages
if (config.botUrl) {
/** type {import('./whapi').Settings} */
const settings = {
webhooks: [
{
url: config.botUrl,
events: [
// default event for getting messages
{type: "messages", method: "post"}
],
mode: "method"
}
]
}
await sendWhapiRequest('settings', settings, 'PATCH');
}
}
async function handleNewMessages(req, res){ // handle messages
try {
/** type {import('./whapi').Message[]} */
const messages = req?.body?.messages;
for (let message of messages) {
if (message.from_me) continue;
/** type {import('./whapi').Sender} */
const sender = {
to: message.chat_id
}
let endpoint = 'messages/text';
const command = Object.keys(COMMANDS)[+message.text?.body?.trim() - 1];
switch (command) { // depending on the command, perform an action
case 'TEXT': {
sender.body = 'Simple text message';
break;
}
case 'IMAGE': {
sender.caption = 'Text under the photo.';
sender.media = fs.createReadStream(FILES.IMAGE); // read file
endpoint = 'messages/image';
break;
}
case 'DOCUMENT': {
sender.caption = 'Text under the document.';
sender.media = fs.createReadStream(FILES.DOCUMENT);
endpoint = 'messages/document';
break;
}
case 'VIDEO': {
sender.caption = 'Text under the video.';
sender.media = fs.createReadStream(FILES.VIDEO);
endpoint = 'messages/video';
break;
}
case 'CONTACT': {
sender.name = 'Whapi Test';
sender.vcard = fs.readFileSync(FILES.VCARD).toString();
endpoint = 'messages/contact';
break;
}
case 'PRODUCT': {
/* you can get real product id using endpoint https://whapi.readme.io/reference/getproducts */
endpoint = `business/products/${config.product}`;
break;
}
case 'GROUP_CREATE': {
/* Warning : you can create group only with contacts from phone contact list */
const res = await sendWhapiRequest(`groups`, {subject: 'Whapi.Cloud Test', participants: [message.chat_id.split('@')[0]]});
sender.body = res.group_id ? `Group created. Group id: ${res.group_id}` : 'Error';
break;
}
case 'GROUP_TEXT': {
/*To get group id, use /groups endpoint */
sender.to = config.group;
sender.body = 'Simple text message for the group';
break;
}
case 'GROUPS_IDS': { // get groups
const {groups} = await sendWhapiRequest('groups', {count: 3}, 'GET');
sender.body = groups && groups.reduce((prevVal, currVal, i) => {
return i === 0 ? `${currVal.id} - ${currVal.name}` : prevVal + ',\n ' + `${currVal.id} - ${currVal.name}`;
}, '') || 'No groups';
break;
}
default: { // if command not found - set text message with commands
sender.body = 'Hi. Send me a number from the list. Don\'t forget to change the actual data in the code! \n\n' +
Object.values(COMMANDS).map((text, i) => `${i + 1}. ${text}`).join('\n');
}
}
await sendWhapiRequest(endpoint, sender); // send request
}
res.send('Ok');
} catch (e) {
res.send(e.message);
}
}
// Create a new instance of express
const app = express();
app.use(bodyParser.json());
app.get('/', function (req, res) {
res.send('Bot is running');
});
app.post('/hook/messages', handleNewMessages); // route for get messages
setHook().then(() => {
const port = config.port || (config.botUrl.indexOf('https:') === 0 ? 443 : 80) // if port not set - set port 443 (if https) or 80 (if http)
app.listen(port, function () {
console.log(`Listening on port ${port}...`);
});
});
- Inicializamos el servidor con Express e instalamos un webhook para manejar los mensajes entrantes.
- Cuando se recibe un mensaje de texto, el bot responde repitiendo el texto recibido.
- Para enviar mensajes, utilizamos las funciones sendTextMessage y sendImageMessage.