186 lines
6.7 KiB
JavaScript
186 lines
6.7 KiB
JavaScript
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', `🚀 nucleo‑bot escuchando en :${PORT}`);
|
||
bootstrap().catch(err => log('error', 'Error en bootstrap:', err.message));
|
||
});
|