Files
whatsapp_bot/nucleo-bot/indexv1.js
2025-05-02 13:09:42 -06:00

186 lines
6.7 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import express from 'express';
import axios from 'axios';
import morgan from 'morgan';
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc.js'; // ⬅OJO: “.js” si usás ESM puro
dayjs.extend(utc);
// ───────────────────────────────────────────
// ⚙️ Variables de entorno (con defaults)
const VERSION = '0.3.21';
const API_URL = process.env.BOT_API_URL ?? 'http://whatsapp-bot:8002';
const GROUP_ID = process.env.GROUP_ID ?? '120363203056794284@g.us';
const REPLY_MSG = process.env.REPLY_MSG ?? 'que pedos';
const PORT = +(process.env.PORT ?? 4000);
const LOG_LEVEL = process.env.LOG_LEVEL ?? 'debug';
const RETRY_MS = +(process.env.RETRY_MS ?? 5_000); // 5 s
const MAX_ATTEMPTS = +(process.env.MAX_ATTEMPTS ?? 60);// 5 min máx
// ───────────────────────────────────────────
// ► Logger interno
function log(level, ...args) {
const levels = ['debug', 'info', 'warn', 'error'];
if (levels.indexOf(level) >= levels.indexOf(LOG_LEVEL)) {
console[level === 'debug' ? 'log' : level](
`[${dayjs().utc().format()}]`, level.toUpperCase(), ...args
);
}
}
// ───────────────────────────────────────────
const app = express();
app.use(express.json({ limit: '1mb' }));
app.use(morgan('[:date[iso]] :method :url :status - :response-time ms'));
// ► Util para enviar texto
async function sendText(to, content) {
log('info', `Enviando mensaje a ${to}: "${content}"`);
const { data } = await axios.post(`${API_URL}/sendText`, { args: { to, content } });
log('debug', 'Respuesta de /sendText →', data);
return data;
}
// ► Procesar mensajes entrantes
async function processIncoming(msg) {
const text = msg.body ?? msg.text ?? '';
// 🧠 Extraemos info útil
const info = {
idMensaje: msg.id ?? msg.mId ?? null,
texto: text,
esDeGrupo: msg.isGroupMsg ?? false,
grupoId: msg.chatId ?? null,
nombreGrupo: msg.chat?.name ?? msg.chat?.formattedTitle ?? null,
participantesGrupo: msg.chat?.participantsCount ?? null,
esPrivado: !(msg.isGroupMsg ?? false),
autorId: msg.sender?.id ?? msg.author ?? null,
autorNombre: msg.sender?.name ?? null,
autorPushName: msg.sender?.pushname ?? null,
autorEsContacto: msg.sender?.isMyContact ?? false,
reenviado: msg.isForwarded ?? false,
menciones: msg.mentionedJidList ?? [],
citandoMensaje: msg.isQuotedMsgAvailable ?? false,
tieneReaccion: msg.hasReaction ?? false,
tipo: msg.type ?? 'chat',
timestamp: msg.timestamp ?? null,
fecha: msg.timestamp ? dayjs.unix(msg.timestamp).format('YYYY-MM-DD HH:mm:ss') : null,
};
// 📋 Logueamos limpio
log('debug', '↪︎ Mensaje IN procesado', info);
// 🚀 Acción si menciona al bot
if (/@nucleo/i.test(info.texto)) {
await sendText(GROUP_ID, REPLY_MSG);
}
// 🚀 Acción si pide repetir
if (/@nucleoRepeti/i.test(info.texto)) {
const partes = info.texto.split(/@nucleoRepeti/i);
const contenido = (partes[1]?.trim() || 'vacio');
log('info', `📢 Reenviando: "${contenido}" al chat ${info.grupoId}`);
await sendText(info.grupoId, contenido);
}
}
// ──────── ENDPOINTS ─────────────────────────
app.post('/webhook', async (req, res) => {
const { event, data } = req.body;
log('debug', `📩 Webhook event "${event}"`);
if (event === 'onMessage' || event === 'onAnyMessage') await processIncoming(data);
res.sendStatus(200);
});
app.get('/debug/scan', async (_req, res) => {
const { data } = await axios.post(`${API_URL}/loadAndGetAllMessagesInChat`, {
args: { chatId: GROUP_ID, includeMe: 'true', includeNotifications: 'false' }
});
const msgs = (data?.response ?? []).slice(-20);
log('info', `Escaneando ${msgs.length} mensajes recientes…`);
for (const m of msgs) await processIncoming(m);
res.json({ ok: true, scanned: msgs.length });
});
app.get('/debug/send', async (req, res) => {
const text = req.query.msg ?? REPLY_MSG;
const resp = await sendText(GROUP_ID, text);
res.json({ ok: true, resp });
});
app.get('/debug/version', (_req, res) => {
res.json({ version: VERSION });
});
// ────────── INICIALIZACIÓN ──────────────────
async function waitForGateway() {
for (let i = 1; i <= MAX_ATTEMPTS; i++) {
try {
await axios.get(`${API_URL}/api-docs`); // endpoint de salud de open-wa
log('info', '🟢 whatsapp-gateway listo');
return;
} catch {
log('warn', `Gateway no responde (intento ${i}/${MAX_ATTEMPTS})…`);
await new Promise(r => setTimeout(r, RETRY_MS));
}
}
throw new Error('whatsapp-gateway no respondió a tiempo');
}
async function clearWebhooks() {
try {
const { data } = await axios.post(`${API_URL}/listWebhooks`);
const hooks = data?.response ?? [];
if (!hooks.length) {
log('info', 'Sin webhooks previos que limpiar');
return;
}
log('info', `Eliminando ${hooks.length} webhooks…`);
const results = await Promise.allSettled(
hooks.map(h => axios.post(`${API_URL}/removeWebhook`, { args: { webhookId: h.id } }))
// ⬆️ NOTA: ahora es "webhookId", no "id"
);
results.forEach((result, idx) => {
const id = hooks[idx].id;
if (result.status === 'fulfilled' && result.value?.data?.response === true) {
log('debug', `✔️ Eliminado webhook ${id}`);
} else {
log('warn', `⚠️ Falló eliminar webhook ${id}`);
}
});
const okCount = results.filter(r => r.status === 'fulfilled' && r.value?.data?.response === true).length;
log('info', `Limpieza OK (${okCount}/${hooks.length} eliminados)`);
} catch (e) {
log('error', 'Fallo limpiando webhooks:', e.response?.data ?? e.message);
}
}
async function registerWebhook() {
const url = `http://nucleo-bot:${PORT}/webhook`;
const { data } = await axios.post(`${API_URL}/registerWebhook`, {
args: { url, events: ['onAnyMessage'], id: 'nucleo-bot' }
});
log('info', '✔️ Webhook registrado:', data);
}
async function bootstrap() {
await waitForGateway();
await clearWebhooks();
await registerWebhook();
}
app.listen(PORT, () => {
log('info', `🪪 Versión del bot: ${VERSION}`);
log('info', `🚀 nucleobot escuchando en :${PORT}`);
bootstrap().catch(err => log('error', 'Error en bootstrap:', err.message));
});