feat: Add Evolution API webhook and utilities
This commit is contained in:
parent
1ede00aea8
commit
e00faf1ace
85
src/app/api/webhooks/openclaw/route.ts
Normal file
85
src/app/api/webhooks/openclaw/route.ts
Normal 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
67
src/lib/evolution.ts
Normal 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 };
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user