feat(whatsapp): agrupar albumes de fotos en una sola publicacion y seleccionar la mejor foto con IA
This commit is contained in:
parent
1a56049a43
commit
c8fd30e1c8
@ -184,10 +184,44 @@ export async function POST(req: Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// LOGIC 2: PROCESAR NUEVO MENSAJE PARA GEMINI
|
||||
// Evitámos tratar de procesar respuestas cortas o mensajes vacíos
|
||||
if (!text && !messageData.imageMessage && !messageData.videoMessage && !messageData.documentMessage) {
|
||||
return NextResponse.json({ success: true, message: 'Ignored empty message' }, { status: 200 });
|
||||
// Cache en memoria para evitar procesar mensajes duplicados que WhatsApp retransmite en ráfaga
|
||||
const processedMessagesCache = new Map<string, number>();
|
||||
|
||||
function isDuplicateMessage(chatId: string, text: string): boolean {
|
||||
const key = `${chatId}:${text.trim().substring(0, 100)}`;
|
||||
const now = Date.now();
|
||||
const lastSeen = processedMessagesCache.get(key);
|
||||
|
||||
// Limpieza de claves viejas (> 2 minutos)
|
||||
for (const [k, time] of processedMessagesCache.entries()) {
|
||||
if (now - time > 120000) {
|
||||
processedMessagesCache.delete(k);
|
||||
}
|
||||
}
|
||||
|
||||
if (lastSeen && (now - lastSeen) < 45000) {
|
||||
return true;
|
||||
}
|
||||
|
||||
processedMessagesCache.set(key, now);
|
||||
return false;
|
||||
}
|
||||
|
||||
// LOGIC 2: PROCESAR PUBLICACIÓN ÚNICA
|
||||
// En WhatsApp, los álbumes de fotos (ej. 25 fotos + 1 texto) llegan como 26 eventos separados.
|
||||
// Las fotos individuales sin texto sustancial NO deben generar publicaciones independientes;
|
||||
// se recolectan como recursos para la publicación principal que contiene el texto de la noticia.
|
||||
const hasSubstantialText = Boolean(text && text.trim().length >= 15);
|
||||
|
||||
if (!hasSubstantialText) {
|
||||
console.log(`[WhatsApp Webhook] Mensaje multimedia sin texto editorial (${text?.length || 0} chars). Registrado para lote de fotos.`);
|
||||
return NextResponse.json({ success: true, message: 'Media registered for album batch' }, { status: 200 });
|
||||
}
|
||||
|
||||
// Evitar duplicación si WhatsApp envía el mismo texto repetido en la ráfaga
|
||||
if (isDuplicateMessage(remoteJid, text)) {
|
||||
console.log(`[WhatsApp Webhook] Mensaje de texto duplicado en ráfaga ignorado.`);
|
||||
return NextResponse.json({ success: true, message: 'Ignored duplicate text in burst' }, { status: 200 });
|
||||
}
|
||||
|
||||
// Descubrir el grupo central de Notificaciones y Aprobaciones (Notificaciones Almeira)
|
||||
@ -200,10 +234,10 @@ export async function POST(req: Request) {
|
||||
`📥 *Nueva Información Recibida*\n` +
|
||||
`📍 *Fuente:* ${filterResult.resolvedName}\n` +
|
||||
`_Almeira Web • Asistente Editorial con IA_\n\n` +
|
||||
`🤖 Iniciando análisis con IA, estructuración y optimización de contenido...`
|
||||
`🤖 Recolectando lote de imágenes y analizando contenido...`
|
||||
).catch(e => console.warn('[WhatsApp Webhook] No se pudo enviar notificación de inicio:', e.message));
|
||||
|
||||
// Recolectamos todas las imágenes/multimedia candidatas (mensaje actual + lote reciente del mismo chat)
|
||||
// Recolectamos todas las imágenes/multimedia candidatas (mensaje actual + álbum de fotos reciente del chat)
|
||||
const candidateMediaList: Array<{ base64: string; mimeType: string }> = [];
|
||||
|
||||
if (messageData.imageMessage || messageData.videoMessage || messageData.documentMessage) {
|
||||
@ -213,9 +247,9 @@ export async function POST(req: Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// Consultamos si llegaron otras imágenes en el mismo grupo o chat en los últimos 2 minutos (álbum / ráfaga)
|
||||
// Consultamos todas las fotos que llegaron en el mismo grupo en los últimos 4 minutos (álbum completo de hasta 30 fotos)
|
||||
try {
|
||||
const recentMedia = await fetchRecentMediaFromSameChat(remoteJid, 4, 120);
|
||||
const recentMedia = await fetchRecentMediaFromSameChat(remoteJid, 30, 240);
|
||||
for (const rm of recentMedia) {
|
||||
// Evitar duplicar la misma imagen exacta
|
||||
if (!candidateMediaList.some(c => c.base64.slice(0, 80) === rm.base64.slice(0, 80))) {
|
||||
@ -226,7 +260,7 @@ export async function POST(req: Request) {
|
||||
console.warn('[WhatsApp Webhook] No se pudo consultar lote multimedia reciente:', e);
|
||||
}
|
||||
|
||||
console.log(`[WhatsApp Webhook] Procesando ${candidateMediaList.length} imágenes candidatas con Gemini...`);
|
||||
console.log(`[WhatsApp Webhook] Procesando ${candidateMediaList.length} imágenes del álbum con Gemini...`);
|
||||
|
||||
// Llamar a Gemini para estructurar la publicación y seleccionar la mejor imagen
|
||||
const parsed = await processWhatsAppMessage(text, candidateMediaList);
|
||||
|
||||
@ -44,25 +44,25 @@ export async function getBase64FromMediaMessage(messagePayload: any): Promise<{
|
||||
*/
|
||||
export async function fetchRecentMediaFromSameChat(
|
||||
remoteJid: string,
|
||||
maxItems = 3,
|
||||
windowSeconds = 90
|
||||
maxItems: number = 25,
|
||||
windowSeconds: number = 180
|
||||
): Promise<Array<{ base64: string, mimeType: string, messageId: string }>> {
|
||||
if (!EVOLUTION_API_URL || !EVOLUTION_INSTANCE_NAME || !remoteJid) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
const url = `${EVOLUTION_API_URL}/chat/findMessages/${EVOLUTION_INSTANCE_NAME}?page=1&limit=5`;
|
||||
const url = `${EVOLUTION_API_URL}/chat/findMessages/${EVOLUTION_INSTANCE_NAME}?page=1&limit=35`;
|
||||
const response = await axios.post(
|
||||
url,
|
||||
{ where: { "key": { "remoteJid": remoteJid } } },
|
||||
{ headers, timeout: 3000 }
|
||||
{ headers, timeout: 6000 }
|
||||
);
|
||||
|
||||
const records = response.data?.messages?.records || [];
|
||||
const nowSec = Math.floor(Date.now() / 1000);
|
||||
|
||||
// Strict check: only messages created within the last `windowSeconds` (90s)
|
||||
// Strict check: only messages created within the last `windowSeconds` (180s)
|
||||
const mediaRecords = records.filter((r: any) => {
|
||||
const isMedia = Boolean(r.message?.imageMessage || r.message?.videoMessage);
|
||||
if (!isMedia || !r.messageTimestamp) return false;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user