import { NextResponse } from 'next/server'; import { ID, Query } from 'node-appwrite'; import { InputFile } from 'node-appwrite/file'; import { createAdminClient } from '@/lib/appwrite.server'; import { APPWRITE_ALMEIRA_CONFIG } from '@/lib/appwrite'; import { processWhatsAppMessage } from '@/lib/gemini'; import { sendWhatsAppMessage, getBase64FromMediaMessage } from '@/lib/evolution'; // Grupo de publicaciones (tomado del test) o puedes dejarlo abierto a todos los grupos const TARGET_GROUP_JID = '573134848203-1408546044@g.us'; // Función para obtener el último día del mes actual function getLastDayOfMonth(): string { const date = new Date(); const lastDay = new Date(date.getFullYear(), date.getMonth() + 1, 0); return lastDay.toISOString().split('T')[0]; } function generateSlug(titulo: string): string { return titulo.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[^a-z0-9\s-]/g, '').trim().replace(/\s+/g, '-').replace(/-+/g, '-').substring(0, 100); } function truncateString(str: string, length: number): string { if (!str) return ''; return str.length > length ? str.substring(0, length - 3) + '...' : str; } export async function POST(req: Request) { try { const payload = await req.json(); const eventType = payload.event; // Solo procesamos nuevos mensajes if (eventType !== 'messages.upsert') { return NextResponse.json({ success: true, message: 'Ignored event' }, { status: 200 }); } const data = payload.data; const remoteJid = data?.key?.remoteJid; const fromMe = data?.key?.fromMe; const messageData = data?.message || {}; const rawText = messageData.conversation || messageData.extendedTextMessage?.text || messageData.imageMessage?.caption || messageData.videoMessage?.caption || ''; // Ignorar mensajes generados por el bot para evitar bucle infinito, // pero permitir tests que el administrador envíe desde el mismo celular del bot. if (fromMe && (rawText.includes('📰 *Publicación Procesada') || rawText.includes('✅ La publicación ha sido aprobada') || rawText.includes('❌ Error al publicar'))) { return NextResponse.json({ success: true, message: 'Ignored bot self-message loop' }, { status: 200 }); } // Filtrar estrictamente para que SOLO procese mensajes del grupo 'Publicaciones Almeira' // (A petición del usuario se desactiva para procesar todos los chats y filtrar en la fase de aprobación) // if (remoteJid !== TARGET_GROUP_JID) { // return NextResponse.json({ success: true, message: 'Ignored other chat' }, { status: 200 }); // } // Extraer texto del mensaje (ya guardado en rawText, lo mantenemos como text para retrocompatibilidad interna) const text = rawText; const lowerText = text.trim().toLowerCase(); const contextInfo = messageData.extendedTextMessage?.contextInfo; const isReply = !!contextInfo?.stanzaId; const quotedText = contextInfo?.quotedMessage?.conversation || contextInfo?.quotedMessage?.extendedTextMessage?.text || ''; const { databases, storage } = await createAdminClient(); // LOGIC 1: APROBAR PUBLICACIÓN if (isReply && lowerText.includes('aprobar')) { // Extraer ID de la referencia en el mensaje del bot const refMatch = quotedText.match(/Ref:\s*([a-zA-Z0-9_-]+)/); if (refMatch && refMatch[1]) { const postId = refMatch[1]; try { // Actualizar Appwrite await databases.updateDocument( APPWRITE_ALMEIRA_CONFIG.databaseId, APPWRITE_ALMEIRA_CONFIG.collections.posts, postId, { estado: 'publicado' } ); await sendWhatsAppMessage(remoteJid, `✅ La publicación ha sido aprobada y publicada exitosamente.\n(ID: ${postId})`); return NextResponse.json({ success: true, message: 'Post aprobado' }, { status: 200 }); } catch (e: any) { console.error("Error approving post:", e); await sendWhatsAppMessage(remoteJid, `❌ Error al publicar: ${e.message}`); return NextResponse.json({ success: false, error: e.message }, { status: 500 }); } } } // LOGIC 2: PROCESAR NUEVO MENSAJE PARA GEMINI // Evitámos tratar de procesar respuestas cortas o mensajes vacíos if (!text && !messageData.imageMessage) { return NextResponse.json({ success: true, message: 'Ignored empty message' }, { status: 200 }); } // Verificamos si tiene multimedia let base64Media = undefined; let mimeType = undefined; let imageId = undefined; if (messageData.imageMessage || messageData.videoMessage || messageData.documentMessage) { const media = await getBase64FromMediaMessage(data); if (media) { base64Media = media.base64; mimeType = media.mimetype; // Subimos a Appwrite Storage const buffer = Buffer.from(base64Media, 'base64'); // Appwrite requiere un InputFile o similar. node-appwrite maneja InputFile.fromBuffer const extension = mimeType.split('/')[1] || 'jpg'; const fileName = `wa_${Date.now()}.${extension}`; const file = InputFile.fromBuffer(buffer, fileName); const uploadedFile = await storage.createFile( APPWRITE_ALMEIRA_CONFIG.mediaBucketId, ID.unique(), file ); imageId = uploadedFile.$id; } } // Llamar a Gemini const parsed = await processWhatsAppMessage(text, base64Media, mimeType); // Determinar fecha de vencimiento let fechaVencimiento = parsed.fecha_vencimiento; if ((parsed.tipo === 'evento' || parsed.tipo === 'aviso') && !fechaVencimiento) { fechaVencimiento = getLastDayOfMonth(); } // Buscar un tipo de publicación por defecto (Requerido por Appwrite Schema) let tipoPublicacionId = ''; try { const types = await databases.listDocuments( APPWRITE_ALMEIRA_CONFIG.databaseId, APPWRITE_ALMEIRA_CONFIG.collections.publicationTypes, [] ); if (types.documents.length > 0) { tipoPublicacionId = types.documents[0].$id; } } catch (e) { console.warn('No se pudo obtener un tipo de publicación por defecto:', e); } // Crear Post en Appwrite const newPost = await databases.createDocument( APPWRITE_ALMEIRA_CONFIG.databaseId, APPWRITE_ALMEIRA_CONFIG.collections.posts, ID.unique(), { titulo: truncateString(parsed.titulo, 70), slug: generateSlug(parsed.titulo) + '-' + Date.now().toString().slice(-4), tipo: parsed.tipo, resumen_redes: truncateString(parsed.seo_descripcion, 155), seo_titulo: truncateString(parsed.titulo, 60), seo_descripcion: truncateString(parsed.seo_descripcion, 155), contenido: parsed.contenido, estado: 'borrador', // Se requiere aprobación fecha_publicacion: new Date().toISOString(), ...(tipoPublicacionId && { tipo_publicacion_id: tipoPublicacionId }), ...(fechaVencimiento && { fecha_finalizacion: new Date(fechaVencimiento).toISOString() }), ...(imageId && { imagen_destacada_id: imageId }) } ); // Notificar al grupo de WhatsApp pidiendo aprobación const requestText = `📰 *Publicación Procesada (Pendiente de Aprobación)*\n\n` + `*Título:* ${parsed.titulo}\n` + `*Tipo:* ${parsed.tipo.toUpperCase()}\n` + `*SEO:* ${parsed.seo_descripcion}\n\n` + `Para aprobar esta publicación, responde a este mismo mensaje con la palabra "Aprobar".\n\n` + `(Ref: ${newPost.$id})`; await sendWhatsAppMessage(remoteJid, requestText); return NextResponse.json({ success: true, message: 'Mensaje procesado' }, { status: 200 }); } catch (error: any) { console.error('Error in WhatsApp Webhook:', error); return NextResponse.json({ success: false, error: error.message }, { status: 500 }); } }