primer commit

This commit is contained in:
2025-05-02 13:09:42 -06:00
commit 42793abc7a
21 changed files with 5872 additions and 0 deletions

185
nucleo-bot/indexv1.js Normal file
View File

@@ -0,0 +1,185 @@
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));
});