// /respuesta/respuestaNormal.js import fs from 'fs/promises'; import { log } from '../logger.js'; // <- Nota el '../' import { sendText, fetchChatMessages, setTypingStatus } from '../whatsapp.js'; // <- Nota el '../' import { askGemini } from '../gemini.js'; // <- Nota el '../' import { processMessage } from '../utils/processMessage.js'; // <- Nota el '../' import { saveMedia } from '../utils/saveMedia.js'; // <- Nota el '../' /* Quita campos pesados antes de mandar a Gemini */ const cleanForGemini = ({ media, reactions, preview, ...rest }) => rest; /** * Procesa la lógica específica para el comando @nucleo. * Obtiene el historial, prepara el contexto, procesa media, * llama a Gemini y envía la respuesta. * @param {object} msg - El objeto de mensaje procesado. */ export async function respuestaNormal(msg) { const text = msg.text || ''; // Necesitamos el texto original aquí también setTypingStatus(msg.chatId, true); log('info', '🧠 Generando respuesta para @nucleo…'); try { /* 1) historial completo del chat */ const allraw = await fetchChatMessages(msg.chatId); const allMsgs = allraw.map(processMessage); /* 2) recorta contexto a ≤100 kB */ const context = allMsgs.map(cleanForGemini); let json = JSON.stringify(context); while (json.length > 100_000 && context.length) { context.shift(); json = JSON.stringify(context); } // Asegurarse de que el contexto no esté vacío antes de acceder a context[0] const chatName = context.length > 0 ? context[0]?.chatName : 'chat'; /* 3) prompt */ const prompt = [ `Eres el asistente del grupo "${chatName}".`, `Pregunta del usuario: ${text}` // Usamos el 'text' del mensaje original que activó el comando , ...context]; /* 4) procesa medias y respeta el límite de 20 MB */ // Filtrar solo los mensajes que no son de tipo 'chat' del historial obtenido const medias = allraw.filter(m => m.type !== 'chat'); const settled = await Promise.allSettled(medias.map(saveMedia)); const MAX = 20 * 1024 * 1024; // 20 MB let total = 0; const files = {}; for (const r of settled) { if (r.status !== 'fulfilled' || !r.value) continue; if (typeof r.value !== 'object' || r.value === null) continue; const entries = Object.entries(r.value); if (entries.length === 0) continue; const [msgId, obj] = entries[0]; if (typeof obj !== 'object' || obj === null) continue; let size = Number(obj.sizeBytes || 0); if (!size && obj.filePath) { try { size = (await fs.stat(obj.filePath)).size; } catch { size = 0; } } // Comprobar si añadir este archivo excede el límite MÁXIMO TOTAL // y si ya tenemos *algo* (total > 0) para evitar empezar con un archivo demasiado grande if (total + size > MAX && total > 0) break; // Si este archivo *por sí solo* excede el límite, saltarlo if (size > MAX) continue; total += size; files[msgId] = { uri: obj.uri || obj.filePath, mimeType: obj.mimeType }; } // log('info', '🧠 Enviando a Gemini...', { files }); /* 5) llama a Gemini y responde */ const respuesta = await askGemini(prompt, files); await sendText(msg.chatId, respuesta); log('info', '🧠 Respuesta enviada'); } catch (error) { log('error', 'Error en respuestaNormal:', error); await sendText(msg.chatId, 'Hubo un error al procesar tu solicitud.'); } finally { setTypingStatus(msg.chatId, false); } }