feat: Add Evolution API webhook and utilities

This commit is contained in:
Carlos (Almeira) 2026-06-21 11:54:55 -05:00
parent 1ede00aea8
commit e00faf1ace
2 changed files with 152 additions and 0 deletions

View File

@ -0,0 +1,85 @@
import { NextResponse } from 'next/server';
import { ID } from 'node-appwrite';
import { createAdminClient } from '@/lib/appwrite.server';
import { APPWRITE_ALMEIRA_CONFIG } from '@/lib/appwrite';
import { collectionMap } from '@/lib/schema';
// Espera un token de seguridad para evitar spam
const OPENCLAW_SECRET = process.env.OPENCLAW_WEBHOOK_SECRET || 'secret-openclaw-token';
export async function POST(req: Request) {
try {
// 1. Verificar autenticación básica mediante Header o Query
const { searchParams } = new URL(req.url);
const token = searchParams.get('token') || req.headers.get('authorization')?.replace('Bearer ', '');
if (token !== OPENCLAW_SECRET) {
return NextResponse.json({ success: false, error: 'No autorizado. Token inválido.' }, { status: 401 });
}
// 2. Parsear la información enviada por OpenClaw
const payload = await req.json();
// Validar campos mínimos
if (!payload.titulo || !payload.contenido) {
return NextResponse.json({
success: false,
error: 'Faltan campos requeridos. Se espera "titulo" y "contenido".'
}, { status: 400 });
}
const { databases } = await createAdminClient();
const postsCollectionId = collectionMap['posts'];
// 3. Preparar el documento de la noticia
// Convertimos el título en un slug básico (ej: "Mi Noticia" -> "mi-noticia")
const slug = payload.titulo
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/(^-|-$)+/g, '');
// Valores por defecto para relaciones requeridas según el schema
let tipoPublicacionId = payload.tipo_publicacion_id;
// Si OpenClaw no envía el tipo_publicacion_id, buscamos uno por defecto
if (!tipoPublicacionId) {
try {
const pubTypesCollection = collectionMap['publication_types'];
const types = await databases.listDocuments(APPWRITE_ALMEIRA_CONFIG.databaseId, pubTypesCollection, []);
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);
}
}
const postData = {
titulo: payload.titulo,
slug: slug,
tipo: payload.tipo || 'noticia',
estado: 'borrador', // Se guarda como borrador por seguridad
fecha_publicacion: new Date().toISOString(),
contenido: payload.contenido,
tipo_publicacion_id: tipoPublicacionId
};
// 4. Guardar en Appwrite
const document = await databases.createDocument(
APPWRITE_ALMEIRA_CONFIG.databaseId,
postsCollectionId,
ID.unique(),
postData
);
return NextResponse.json({
success: true,
message: 'Noticia creada exitosamente',
data: { id: document.$id, titulo: document.titulo }
}, { status: 201 });
} catch (error: any) {
console.error('Error in OpenClaw Webhook:', error);
return NextResponse.json({ success: false, error: error.message }, { status: 500 });
}
}

67
src/lib/evolution.ts Normal file
View File

@ -0,0 +1,67 @@
import axios from 'axios';
const EVOLUTION_API_URL = process.env.EVOLUTION_API_URL || '';
const EVOLUTION_API_KEY = process.env.EVOLUTION_API_KEY || '';
const EVOLUTION_INSTANCE_NAME = process.env.EVOLUTION_INSTANCE_NAME || '';
/**
* Send a simple text message via Evolution API
*/
export async function sendWhatsAppMessage(number: string, text: string) {
if (!EVOLUTION_API_URL || !EVOLUTION_API_KEY || !EVOLUTION_INSTANCE_NAME) {
console.warn('Evolution API no está configurada en las variables de entorno.');
return { success: false, error: 'Configuración faltante' };
}
try {
const response = await axios.post(
`${EVOLUTION_API_URL}/message/sendText/${EVOLUTION_INSTANCE_NAME}`,
{
number,
text
},
{
headers: {
'apikey': EVOLUTION_API_KEY,
'Content-Type': 'application/json'
}
}
);
return { success: true, data: response.data };
} catch (error: any) {
console.error('Error al enviar mensaje de WhatsApp:', error?.response?.data || error.message);
return { success: false, error: error.message };
}
}
/**
* Send an image with text/caption via Evolution API
*/
export async function sendWhatsAppImage(number: string, imageUrl: string, caption: string) {
if (!EVOLUTION_API_URL || !EVOLUTION_API_KEY || !EVOLUTION_INSTANCE_NAME) {
console.warn('Evolution API no está configurada en las variables de entorno.');
return { success: false, error: 'Configuración faltante' };
}
try {
const response = await axios.post(
`${EVOLUTION_API_URL}/message/sendMedia/${EVOLUTION_INSTANCE_NAME}`,
{
number,
mediatype: 'image',
media: imageUrl,
caption
},
{
headers: {
'apikey': EVOLUTION_API_KEY,
'Content-Type': 'application/json'
}
}
);
return { success: true, data: response.data };
} catch (error: any) {
console.error('Error al enviar imagen de WhatsApp:', error?.response?.data || error.message);
return { success: false, error: error.message };
}
}