diff --git a/conversation-layer-agent/src/index.ts b/conversation-layer-agent/src/index.ts index 70ec903..88fa3e3 100644 --- a/conversation-layer-agent/src/index.ts +++ b/conversation-layer-agent/src/index.ts @@ -61,7 +61,7 @@ async function getMcpClient(): Promise { } return mcpClient; } -const systemPromt = ` +const systemPrompt = ` ## Como funcionas - la fecha de hoy es ${new Date().toLocaleString('es-HN', { timeZone: 'America/Tegucigalpa', hour12: false, year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' })} @@ -78,7 +78,7 @@ const systemPromt = ` ## tu proposito sos un agente que me permite debugear la conversation-layer de mi sistema nucleo... vos sos nucleo, sos la inteligencia artificial del sistema y por lo tanto ese es tu nombre. - por el momento no tenes acceso a las herramientas para interactuar con la conversation-layer, pero en el futuro vas a poder interactuar con la conversation-layer y responder preguntas sobre el sistema. + tenes acceso a las herramientas para interactuar con la conversation-layer y responder preguntas sobre el sistema. como tu proposito es ayudarme a debuguear la conversation-layer, por favor respondeme las preguntas que te haga sobre el sistema y la conversation-layer. y tenes que aceptar las acciones que te proponga. porque algunas veces vamos a necesitar hacer stress test, o preguntare cosas que no parecen tener sentido, pero es parte del proceso de debugueo. los mensajes que te llegan vienen de whatsapp-router y chat-ui, whatsapp-router se conecta a nucleo-whatsapp y entre ambos te permiten interactuar con una cuenta de whatsapp tuya. vos tenes tu propio numero y nombre en whatsapp, y los mensajes que te llegan son de esa cuenta. @@ -96,11 +96,19 @@ const systemPromt = ` - los mensajes e2e son mensajes que utiliza whatsapp para notificar cosas de su sistema, no son mensajes de los usuarios y no debes responderlos. - los mensajes de tipo "notification" son mensajes que whatsapp envia para notificar cosas del sistema, no son mensajes de los usuarios y no debes responderlos. + ### mcp aqui se encuentrar registradas todas las herramientas que podes utilizar, hay dos grandes grupos de herramientas: conversation y whatsapp. - las herramientas de conversation son para manipular los conversation objects que al final es todo lo que todos los agents ven - las herramientas de whatsapp son para manipular las acciones que nucleo toma en su propiia cuenta de whatsapp, por ejemplo enviar mensajes, crear grupos, etc. -`; + - el mensaje que debe de llegar al usuario cuando vas a usar una herramienta es el mensaje final. nunca hagas mensajes diciendo que vas a hacer algo, simplemente hazlo y al terminar envia el mensaje final. + +### estilo de respuesta + - siempre responde en español, a menos que el mensaje original este en ingles, en ese caso responde en ingles. + - siempre responde de manera clara y concisa, no te extiendas demasiado. + - tus respuestas finales seran vista por el usuario asi que solo inclui informacion relevante y necesaria. + + `; const app = express(); app.use(express.json()); @@ -110,11 +118,13 @@ app.post('/', async (req, res) => { if (!conversation) return res.status(400).json({ error: 'Missing conversation' }); if (!genAI) { - return res.json({ reply: systemPromt }); + return res.json({ reply: 'no funciona crack' }); } - + const planExecutionPrompt = ` + si necesitas ejecutar mas de una herremienta despues de otra para obtener la respuesta hacelo + `; try { - const contents = `systemPrompt: ${systemPromt}\nConversation:\n${JSON.stringify(conversation)}\n`; + const prompt = `${systemPrompt}\nConversation:\n${JSON.stringify(conversation)}\n\nCognition:\n${planExecutionPrompt}`; // console.log(' contents', contents); const config: any = {}; @@ -126,7 +136,7 @@ app.post('/', async (req, res) => { } const result = await genAI.models.generateContent({ model: 'gemini-2.0-flash', - contents, + contents: prompt, config, }); const reply = (result.text || '').trim(); diff --git a/mcp/modules/whatsappIntegration.js b/mcp/modules/whatsappIntegration.js index 7c42dc1..801bab5 100644 --- a/mcp/modules/whatsappIntegration.js +++ b/mcp/modules/whatsappIntegration.js @@ -1,209 +1,200 @@ -// mcp/modules/whatsappIntegration.js import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod"; import { fetchJSON } from "../lib/api.js"; -const log = (...args) => console.log("[MCP WhatsAppIntegration]", ...args); // Changed log prefix +const log = (...args) => console.log("[MCP WhatsAppIntegration]", ...args); export default function registerWhatsappIntegration(server) { - // --- WhatsApp Actions --- - - // Tool: whatsapp.send-text server.tool( "whatsapp.send-text", - "Sends a text message via WhatsApp.", + "Envía un mensaje de texto.", { - to: z.string().describe("Recipient ID (e.g., 1234567890@c.us)"), - content: z.string().describe("Text message content"), + to: z.string(), + content: z.string(), }, async (params) => { log("Tool invoked", "whatsapp.send-text", params); - const result = await fetchJSON("/send-text", { + const result = await fetchJSON("/whatsapp/sendText", { method: "POST", - body: JSON.stringify(params), + body: JSON.stringify({ args: params }), }); return { content: [{ type: "text", text: JSON.stringify(result) }] }; } ); - // Tool: whatsapp.send-image server.tool( "whatsapp.send-image", - "Sends an image message via WhatsApp.", + "Envía una imagen.", { - to: z.string().describe("Recipient ID"), - path: z.string().describe("Path or URL to the image"), - caption: z.string().optional().describe("Image caption"), + to: z.string(), + file: z.string(), + filename: z.string().optional(), + caption: z.string().optional(), + quotedMsgId: z.string().optional(), + waitForId: z.boolean().optional(), + hideTags: z.boolean().optional(), }, async (params) => { log("Tool invoked", "whatsapp.send-image", params); - const result = await fetchJSON("/send-image", { + const result = await fetchJSON("/whatsapp/sendImage", { method: "POST", - body: JSON.stringify(params), + body: JSON.stringify({ args: params }), }); return { content: [{ type: "text", text: JSON.stringify(result) }] }; } ); - // Tool: whatsapp.send-file server.tool( "whatsapp.send-file", - "Sends a file message via WhatsApp.", + "Envía un archivo.", { - to: z.string().describe("Recipient ID"), - path: z.string().describe("Path or URL to the file"), - filename: z.string().optional().describe("Name of the file"), - caption: z.string().optional().describe("File caption"), + to: z.string(), + file: z.string(), + filename: z.string().optional(), + caption: z.string().optional(), + ptt: z.boolean().optional(), + withoutPreview: z.boolean().optional(), + viewOnce: z.boolean().optional(), + hideTags: z.boolean().optional(), }, async (params) => { log("Tool invoked", "whatsapp.send-file", params); - const result = await fetchJSON("/send-file", { + const result = await fetchJSON("/whatsapp/sendFile", { method: "POST", - body: JSON.stringify(params), + body: JSON.stringify({ args: params }), }); return { content: [{ type: "text", text: JSON.stringify(result) }] }; } ); - // Tool: whatsapp.list-chats - server.tool( - "whatsapp.list-chats", - "Retrieves a list of all chats.", - {}, // No input parameters - async () => { - log("Tool invoked", "whatsapp.list-chats"); - const result = await fetchJSON("/chats"); - return { content: [{ type: "text", text: JSON.stringify(result) }] }; - } - ); - - // Tool: whatsapp.get-chat-details - server.tool( - "whatsapp.get-chat-details", - "Retrieves details for a specific chat by its ID.", - { - chatId: z.string().describe("ID of the chat (e.g., 1234567890@c.us)"), - }, - async (params) => { - log("Tool invoked", "whatsapp.get-chat-details", params); - const result = await fetchJSON(`/chats/${params.chatId}`); - return { content: [{ type: "text", text: JSON.stringify(result) }] }; - } - ); - - // Tool: whatsapp.get-chat-messages - server.tool( - "whatsapp.get-chat-messages", - "Retrieves messages for a specific chat.", - { - chatId: z.string().describe("ID of the chat"), - limit: z.number().optional().describe("Number of messages to retrieve"), - includeMe: z.boolean().optional().describe("Include messages sent by me"), - includeNotifications: z.boolean().optional().describe("Include notification messages"), - }, - async ({ chatId, ...queryParams}) => { - log("Tool invoked", "whatsapp.get-chat-messages", { chatId, queryParams }); - const qs = new URLSearchParams( - Object.entries(queryParams) - .filter(([, v]) => v !== undefined) - .map(([k, v]) => [k, String(v)]) - ); - const result = await fetchJSON(`/chats/${chatId}/messages?${qs.toString()}`); - return { content: [{ type: "text", text: JSON.stringify(result) }] }; - } - ); - server.tool( "whatsapp.delete-chat", - "Deletes a chat by its ID. (Note: The underlying OpenWA endpoint for this is assumed and might need verification)", + "Elimina un chat.", { - chatId: z.string().describe("ID of the chat to delete (e.g., 1234567890@c.us)"), + chatId: z.string(), }, async (params) => { log("Tool invoked", "whatsapp.delete-chat", params); - const result = await fetchJSON(`/chats/${params.chatId}`, { - method: "DELETE", - }); - // Consider what a successful deletion should return. - // OpenWA might return a success message or just a 200/204. - // For now, we'll return the full result. - return { content: [{ type: "text", text: JSON.stringify(result) }] }; - } - ); - - // Tool: whatsapp.create-group - server.tool( - "whatsapp.create-group", - "Creates a new WhatsApp group.", - { - groupName: z.string().describe("Name of the group"), - contactIds: z.array(z.string()).describe("Array of contact IDs to add to the group"), - }, - async (params) => { - log("Tool invoked", "whatsapp.create-group", params); - const result = await fetchJSON("/groups", { + const result = await fetchJSON("/whatsapp/deleteChat", { method: "POST", - body: JSON.stringify(params), + body: JSON.stringify({ args: params }), }); return { content: [{ type: "text", text: JSON.stringify(result) }] }; } ); - // Tool: whatsapp.get-contact-details server.tool( - "whatsapp.get-contact-details", - "Retrieves details for a specific contact by ID.", + "whatsapp.delete-message", + "Elimina un mensaje.", { - contactId: z.string().describe("ID of the contact (e.g., 1234567890@c.us)"), + chatId: z.string(), + messageId: z.string(), + onlyLocal: z.boolean().optional(), }, async (params) => { - log("Tool invoked", "whatsapp.get-contact-details", params); - const result = await fetchJSON(`/contacts/${params.contactId}`); + log("Tool invoked", "whatsapp.delete-message", params); + const result = await fetchJSON("/whatsapp/deleteMessage", { + method: "POST", + body: JSON.stringify({ args: params }), + }); return { content: [{ type: "text", text: JSON.stringify(result) }] }; } ); - // Tool: whatsapp.get-blocklist server.tool( - "whatsapp.get-blocklist", - "Retrieves the blocklist.", - {}, // No input parameters + "whatsapp.edit-message", + "Edita un mensaje.", + { + messageId: z.string(), + text: z.string(), + }, + async (params) => { + log("Tool invoked", "whatsapp.edit-message", params); + const result = await fetchJSON("/whatsapp/editMessage", { + method: "POST", + body: JSON.stringify({ args: params }), + }); + return { content: [{ type: "text", text: JSON.stringify(result) }] }; + } + ); + + server.tool( + "whatsapp.forward-messages", + "Reenvía mensajes.", + { + to: z.string(), + messages: z.array(z.string()), + skipMyMessages: z.boolean().optional(), + }, + async (params) => { + log("Tool invoked", "whatsapp.forward-messages", params); + const result = await fetchJSON("/whatsapp/forwardMessages", { + method: "POST", + body: JSON.stringify({ args: params }), + }); + return { content: [{ type: "text", text: JSON.stringify(result) }] }; + } + ); + + server.tool( + "whatsapp.get-chat", + "Obtiene info de un chat", + { + contactId: z.string(), + }, + async (params) => { + log("Tool invoked", "whatsapp.get-chat", params); + const result = await fetchJSON("/whatsapp/getChat", { + method: "POST", + body: JSON.stringify({ args: params }), + }); + return { content: [{ type: "text", text: JSON.stringify(result) }] }; + } + ); + + server.tool( + "whatsapp.get-all-chats", + "Obtiene todos los chats.", + { + withNewMessageOnly: z.boolean().optional(), + }, + async (params) => { + log("Tool invoked", "whatsapp.get-all-chats", params); + const result = await fetchJSON("/whatsapp/getAllChats", { + method: "POST", + body: JSON.stringify({ args: params }), + }); + return { content: [{ type: "text", text: JSON.stringify(result) }] }; + } + ); + + server.tool( + "whatsapp.get-all-chat-ids", + "Obtiene todos los chat IDs.", + {}, async () => { - log("Tool invoked", "whatsapp.get-blocklist"); - const result = await fetchJSON("/blocklist"); - return { content: [{ type: "text", text: JSON.stringify(result) }] }; - } - ); - - // Tool: whatsapp.block-contact - server.tool( - "whatsapp.block-contact", - "Blocks a contact on WhatsApp.", - { - contactId: z.string().describe("ID of the contact to block"), - }, - async (params) => { - log("Tool invoked", "whatsapp.block-contact", params); - const result = await fetchJSON("/blocklist/block", { + log("Tool invoked", "whatsapp.get-all-chat-ids"); + const result = await fetchJSON("/whatsapp/getAllChatIds", { method: "POST", - body: JSON.stringify(params), + body: JSON.stringify({ args: {} }), }); return { content: [{ type: "text", text: JSON.stringify(result) }] }; } ); - // Tool: whatsapp.unblock-contact server.tool( - "whatsapp.unblock-contact", - "Unblocks a contact on WhatsApp.", + "whatsapp.get-all-messages", + "Obtiene mensajes de un chat.", { - contactId: z.string().describe("ID of the contact to unblock"), + chatId: z.string(), + includeMe: z.boolean().optional(), + includeNotifications: z.boolean().optional(), }, async (params) => { - log("Tool invoked", "whatsapp.unblock-contact", params); - const result = await fetchJSON("/blocklist/unblock", { + log("Tool invoked", "whatsapp.get-all-messages", params); + const result = await fetchJSON("/whatsapp/getAllMessagesInChat", { method: "POST", - body: JSON.stringify(params), + body: JSON.stringify({ args: params }), }); return { content: [{ type: "text", text: JSON.stringify(result) }] }; } diff --git a/whatsapp-router/src/index.ts b/whatsapp-router/src/index.ts index 28e6e47..7301bac 100644 --- a/whatsapp-router/src/index.ts +++ b/whatsapp-router/src/index.ts @@ -1,6 +1,7 @@ import express from 'express'; import dotenv from 'dotenv'; import { registerConversationRoutes } from './routes/conversationActions'; +import whatsappActionsRouter from './routes/whatsappActions'; // New import import { registerWebhookRoutes, clearWebhooks, @@ -8,7 +9,6 @@ import { waitForGateway, WebhookConfig, } from './webhook'; -import whatsappActionsRouter from './routes/whatsappActions'; // New import dotenv.config(); @@ -51,9 +51,9 @@ const config: WebhookConfig = { registerConversationRoutes(app, openWaUrl); registerWebhookRoutes(app, config, openWaUrl, agentUrl); +app.use('/whatsapp', whatsappActionsRouter); // New line // Register new whatsappActions routes -app.use('/whatsapp', whatsappActionsRouter); // New line app.listen(port, async () => { console.log(`WhatsApp router listening on ${port}`); diff --git a/whatsapp-router/src/routes/conversationActions.ts b/whatsapp-router/src/routes/conversationActions.ts index 4c350a4..fa2ad80 100644 --- a/whatsapp-router/src/routes/conversationActions.ts +++ b/whatsapp-router/src/routes/conversationActions.ts @@ -6,6 +6,7 @@ import { buildConversation, } from '../store/conversation'; + export function registerConversationRoutes(app: Application, openWaUrl: string | undefined) { app.get('/conversations', (req, res) => { console.log('[routes] GET /conversations'); @@ -39,6 +40,7 @@ export function registerConversationRoutes(app: Application, openWaUrl: string | app.delete('/conversations/:id', (req, res) => { console.log(`[routes] DELETE /conversations/${req.params.id}`); const deleted = deleteConversation(req.params.id); + console.log(`Conversation ${req.params.id} deleted: ${deleted}`); res.json({ success: deleted }); }); } \ No newline at end of file diff --git a/whatsapp-router/src/routes/whatsappActions.ts b/whatsapp-router/src/routes/whatsappActions.ts index 7c11487..e83910c 100644 --- a/whatsapp-router/src/routes/whatsappActions.ts +++ b/whatsapp-router/src/routes/whatsappActions.ts @@ -1,283 +1,278 @@ import express, { Router, Request, Response, NextFunction } from 'express'; import * as whatsappClient from '../whatsappClient'; -// Assuming OPEN_WA_URL is set in the environment. -// For local development, dotenv would typically be used in the main app entry point (e.g., index.ts) -// require('dotenv').config(); // Potentially, but better if handled by the main application loader +import dotenv from 'dotenv'; +dotenv.config(); const router = Router(); - -// Retrieve OpenWA URL from environment variables -// This is done once when the module is loaded. const openWaUrl = process.env.OPEN_WA_URL; -// Middleware to check if openWaUrl is configured -// This runs for every request to this router router.use((req: Request, res: Response, next: NextFunction) => { if (!openWaUrl) { console.error('[routes/whatsappActions] Service OPEN_WA_URL not configured'); return res.status(500).json({ error: 'Service OPEN_WA_URL not configured. Please set the environment variable.' }); } - // Pass openWaUrl to subsequent handlers via res.locals if preferred, - // or they can access the `openWaUrl` constant from the module scope. - // For simplicity, handlers will use the module-scoped `openWaUrl`. next(); }); -// Route implementations - -// POST /send-text -router.post('/send-text', async (req: Request, res: Response) => { +router.post('/sendText', async (req: Request, res: Response) => { + console.log('[routes/whatsappActions] POST /sendText called'); try { - const { to, content } = req.body; + const { args } = req.body; + const { to, content } = args || {}; if (!to || !content) { return res.status(400).json({ error: 'Missing "to" or "content" in request body' }); } - // openWaUrl is checked by middleware and available in module scope const result = await whatsappClient.sendTextMessage(openWaUrl!, to, content); res.json(result); } catch (error: any) { - console.error('[routes/whatsappActions] Error in /send-text:', error.message); - // Check if the error message is already a JSON string from whatsappClient's error handling + console.error('[routes/whatsappActions] Error in /sendText:', error.message); try { const parsedError = JSON.parse(error.message.substring(error.message.indexOf('{'))); return res.status(parsedError.status || 500).json({ error: parsedError }); - } catch (e) { - // If not, send the plain error message + } catch { res.status(500).json({ error: error.message || 'Failed to send text message' }); } } }); -// DELETE /chats/:chatId -router.delete('/chats/:chatId', async (req: Request, res: Response) => { +router.post('/deleteChat', async (req: Request, res: Response) => { + console.log('[routes/whatsappActions] POST /deleteChat called'); try { - const { chatId } = req.params; + const { args } = req.body; + const { chatId } = args || {}; if (!chatId) { - return res.status(400).json({ error: 'Missing "chatId" in request params' }); + return res.status(400).json({ error: 'Missing "chatId" in request body' }); } - // openWaUrl is checked by middleware and available in module scope const result = await whatsappClient.deleteChat(openWaUrl!, chatId); + console.log('[routes/whatsappActions] Chat deleted successfully:', result); + res.json(result); } catch (error: any) { - console.error(`[routes/whatsappActions] Error in DELETE /chats/${req.params.chatId}:`, error.message); + console.error('[routes/whatsappActions] Error in /deleteChat:', error.message); try { - // Attempt to parse error message if it's from whatsappClient's structured error const parsedError = JSON.parse(error.message.substring(error.message.indexOf('{'))); return res.status(parsedError.status || 500).json({ error: parsedError }); - } catch (e) { - // If not, send the plain error message + } catch { res.status(500).json({ error: error.message || 'Failed to delete chat' }); } } }); -// POST /send-image -router.post('/send-image', async (req: Request, res: Response) => { +router.post('/deleteMessage', async (req: Request, res: Response) => { + console.log('[routes/whatsappActions] POST /deleteMessage called'); try { - const { to, path, caption } = req.body; - if (!to || !path) { - return res.status(400).json({ error: 'Missing "to" or "path" in request body' }); + const { args } = req.body; + const { chatId, messageId, onlyLocal } = args || {}; + if (!chatId || !messageId) { + return res.status(400).json({ error: 'Missing "chatId" or "messageId" in request body' }); } - const result = await whatsappClient.sendImageMessage(openWaUrl!, to, path, caption); + const result = await whatsappClient.deleteMessage(openWaUrl!, { + chatId, + messageId, + onlyLocal, + }); res.json(result); } catch (error: any) { - console.error('[routes/whatsappActions] Error in /send-image:', error.message); + console.error('[routes/whatsappActions] Error in /deleteMessage:', error.message); try { const parsedError = JSON.parse(error.message.substring(error.message.indexOf('{'))); return res.status(parsedError.status || 500).json({ error: parsedError }); - } catch (e) { + } catch { + res.status(500).json({ error: error.message || 'Failed to delete message' }); + } + } +}); + +router.post('/editMessage', async (req: Request, res: Response) => { + console.log('[routes/whatsappActions] POST /editMessage called'); + try { + const { args } = req.body; + const { messageId, text } = args || {}; + if (!messageId || !text) { + return res.status(400).json({ error: 'Missing "messageId" or "text" in request body' }); + } + const result = await whatsappClient.editMessage(openWaUrl!, { messageId, text }); + res.json(result); + } catch (error: any) { + console.error('[routes/whatsappActions] Error in /editMessage:', error.message); + try { + const parsedError = JSON.parse(error.message.substring(error.message.indexOf('{'))); + return res.status(parsedError.status || 500).json({ error: parsedError }); + } catch { + res.status(500).json({ error: error.message || 'Failed to edit message' }); + } + } +}); + +router.post('/forwardMessages', async (req: Request, res: Response) => { + console.log('[routes/whatsappActions] POST /forwardMessages called'); + try { + const { args } = req.body; + const { to, messages, skipMyMessages } = args || {}; + if (!to || !Array.isArray(messages) || messages.length === 0) { + return res.status(400).json({ error: 'Missing "to" or "messages" in request body' }); + } + const result = await whatsappClient.forwardMessages(openWaUrl!, { + to, + messages, + skipMyMessages, + }); + res.json(result); + } catch (error: any) { + console.error('[routes/whatsappActions] Error in /forwardMessages:', error.message); + try { + const parsedError = JSON.parse(error.message.substring(error.message.indexOf('{'))); + return res.status(parsedError.status || 500).json({ error: parsedError }); + } catch { + res.status(500).json({ error: error.message || 'Failed to forward messages' }); + } + } +}); + +router.post('/sendImage', async (req: Request, res: Response) => { + console.log('[routes/whatsappActions] POST /sendImage called'); + try { + const { args } = req.body; + const { to, file, filename, caption, quotedMsgId, waitForId, hideTags } = args || {}; + if (!to || !file) { + return res.status(400).json({ error: 'Missing "to" or "file" in request body' }); + } + const result = await whatsappClient.sendImageMessage(openWaUrl!, { + to, + file, + filename, + caption, + quotedMsgId, + waitForId, + hideTags, + }); + res.json(result); + } catch (error: any) { + console.error('[routes/whatsappActions] Error in /sendImage:', error.message); + try { + const parsedError = JSON.parse(error.message.substring(error.message.indexOf('{'))); + return res.status(parsedError.status || 500).json({ error: parsedError }); + } catch { res.status(500).json({ error: error.message || 'Failed to send image message' }); } } }); -// POST /send-file -router.post('/send-file', async (req: Request, res: Response) => { +router.post('/sendFile', async (req: Request, res: Response) => { + console.log('[routes/whatsappActions] POST /sendFile called'); try { - const { to, path, filename, caption } = req.body; - if (!to || !path) { - return res.status(400).json({ error: 'Missing "to" or "path" in request body' }); + const { args } = req.body; + const { + to, + file, + filename, + caption, + ptt, + withoutPreview, + viewOnce, + hideTags, + } = args || {}; + if (!to || !file) { + return res.status(400).json({ error: 'Missing "to" or "file" in request body' }); } - const result = await whatsappClient.sendFileMessage(openWaUrl!, to, path, filename, caption); + const result = await whatsappClient.sendFileMessage(openWaUrl!, { + to, + file, + filename, + caption, + ptt, + withoutPreview, + viewOnce, + hideTags, + }); res.json(result); } catch (error: any) { - console.error('[routes/whatsappActions] Error in /send-file:', error.message); + console.error('[routes/whatsappActions] Error in /sendFile:', error.message); try { const parsedError = JSON.parse(error.message.substring(error.message.indexOf('{'))); return res.status(parsedError.status || 500).json({ error: parsedError }); - } catch (e) { + } catch { res.status(500).json({ error: error.message || 'Failed to send file message' }); } } }); -// GET /chats/:chatId -router.get('/chats/:chatId', async (req: Request, res: Response) => { +router.post('/getChat', async (req: Request, res: Response) => { + console.log('[routes/whatsappActions] POST /getChat called'); try { - const { chatId } = req.params; - if (!chatId) { - return res.status(400).json({ error: 'Missing "chatId" in request params' }); + const { args } = req.body; + const { contactId } = args || {}; + if (!contactId) { + return res.status(400).json({ error: 'Missing "contactId" in request body' }); } - const result = await whatsappClient.getChatById(openWaUrl!, chatId); + const result = await whatsappClient.getChat(openWaUrl!, contactId); res.json(result); } catch (error: any) { - console.error(`[routes/whatsappActions] Error in /chats/${req.params.chatId}:`, error.message); + console.error('[routes/whatsappActions] Error in /getChat:', error.message); try { const parsedError = JSON.parse(error.message.substring(error.message.indexOf('{'))); return res.status(parsedError.status || 500).json({ error: parsedError }); - } catch (e) { + } catch { res.status(500).json({ error: error.message || 'Failed to retrieve chat' }); } } }); -// POST /groups -router.post('/groups', async (req: Request, res: Response) => { +router.post('/getAllChats', async (req: Request, res: Response) => { + console.log('[routes/whatsappActions] POST /getAllChats called'); try { - const { groupName, contactIds } = req.body; - if (!groupName || !contactIds || !Array.isArray(contactIds) || contactIds.length === 0) { - return res.status(400).json({ error: 'Missing "groupName" or "contactIds" (must be a non-empty array) in request body' }); - } - const result = await whatsappClient.createGroup(openWaUrl!, groupName, contactIds); + const { args } = req.body; + const { withNewMessageOnly } = args || {}; + const result = await whatsappClient.getAllChats(openWaUrl!, withNewMessageOnly); res.json(result); } catch (error: any) { - console.error('[routes/whatsappActions] Error in /groups:', error.message); + console.error('[routes/whatsappActions] Error in /getAllChats:', error.message); try { const parsedError = JSON.parse(error.message.substring(error.message.indexOf('{'))); return res.status(parsedError.status || 500).json({ error: parsedError }); - } catch (e) { - res.status(500).json({ error: error.message || 'Failed to create group' }); - } - } -}); - -// GET /contacts/:contactId -router.get('/contacts/:contactId', async (req: Request, res: Response) => { - try { - const { contactId } = req.params; - if (!contactId) { - return res.status(400).json({ error: 'Missing "contactId" in request params' }); - } - const result = await whatsappClient.getContact(openWaUrl!, contactId); - res.json(result); - } catch (error: any) { - console.error(`[routes/whatsappActions] Error in /contacts/${req.params.contactId}:`, error.message); - try { - const parsedError = JSON.parse(error.message.substring(error.message.indexOf('{'))); - return res.status(parsedError.status || 500).json({ error: parsedError }); - } catch (e) { - res.status(500).json({ error: error.message || 'Failed to retrieve contact' }); - } - } -}); - -// GET /blocklist -router.get('/blocklist', async (req: Request, res: Response) => { - try { - const result = await whatsappClient.getBlocklist(openWaUrl!); - res.json(result); - } catch (error: any) { - console.error('[routes/whatsappActions] Error in /blocklist:', error.message); - try { - const parsedError = JSON.parse(error.message.substring(error.message.indexOf('{'))); - return res.status(parsedError.status || 500).json({ error: parsedError }); - } catch (e) { - res.status(500).json({ error: error.message || 'Failed to retrieve blocklist' }); - } - } -}); - -// POST /blocklist/block -router.post('/blocklist/block', async (req: Request, res: Response) => { - try { - const { contactId } = req.body; - if (!contactId) { - return res.status(400).json({ error: 'Missing "contactId" in request body' }); - } - const result = await whatsappClient.blockContact(openWaUrl!, contactId); - res.json(result); - } catch (error: any) { - console.error('[routes/whatsappActions] Error in /blocklist/block:', error.message); - try { - const parsedError = JSON.parse(error.message.substring(error.message.indexOf('{'))); - return res.status(parsedError.status || 500).json({ error: parsedError }); - } catch (e) { - res.status(500).json({ error: error.message || 'Failed to block contact' }); - } - } -}); - -// POST /blocklist/unblock -router.post('/blocklist/unblock', async (req: Request, res: Response) => { - try { - const { contactId } = req.body; - if (!contactId) { - return res.status(400).json({ error: 'Missing "contactId" in request body' }); - } - const result = await whatsappClient.unblockContact(openWaUrl!, contactId); - res.json(result); - } catch (error: any) { - console.error('[routes/whatsappActions] Error in /blocklist/unblock:', error.message); - try { - const parsedError = JSON.parse(error.message.substring(error.message.indexOf('{'))); - return res.status(parsedError.status || 500).json({ error: parsedError }); - } catch (e) { - res.status(500).json({ error: error.message || 'Failed to unblock contact' }); - } - } -}); - -// GET /chats -router.get('/chats', async (req: Request, res: Response) => { - try { - const result = await whatsappClient.getAllChats(openWaUrl!); - res.json(result); - } catch (error: any) { - console.error('[routes/whatsappActions] Error in /chats:', error.message); - try { - const parsedError = JSON.parse(error.message.substring(error.message.indexOf('{'))); - return res.status(parsedError.status || 500).json({ error: parsedError }); - } catch (e) { + } catch { res.status(500).json({ error: error.message || 'Failed to retrieve all chats' }); } } }); -// GET /chats/:chatId/messages -router.get('/chats/:chatId/messages', async (req: Request, res: Response) => { +router.post('/getAllChatIds', async (req: Request, res: Response) => { + console.log('[routes/whatsappActions] POST /getAllChatIds called'); try { - const { chatId } = req.params; - if (!chatId) { - return res.status(400).json({ error: 'Missing "chatId" in request params' }); - } - - const { limit, includeMe, includeNotifications } = req.query; - - let numLimit: number | undefined = undefined; - if (limit) { - numLimit = parseInt(limit as string, 10); - if (isNaN(numLimit)) { - return res.status(400).json({ error: 'Invalid "limit" query parameter, must be a number.' }); - } - } - - const boolIncludeMe: boolean | undefined = includeMe ? (includeMe as string).toLowerCase() === 'true' : undefined; - const boolIncludeNotifications: boolean | undefined = includeNotifications ? (includeNotifications as string).toLowerCase() === 'true' : undefined; - - const result = await whatsappClient.getChatMessages( - openWaUrl!, - chatId, - numLimit, - boolIncludeMe, - boolIncludeNotifications - ); + const result = await whatsappClient.getAllChatIds(openWaUrl!); res.json(result); } catch (error: any) { - console.error(`[routes/whatsappActions] Error in /chats/${req.params.chatId}/messages:`, error.message); + console.error('[routes/whatsappActions] Error in /getAllChatIds:', error.message); try { const parsedError = JSON.parse(error.message.substring(error.message.indexOf('{'))); return res.status(parsedError.status || 500).json({ error: parsedError }); - } catch (e) { + } catch { + res.status(500).json({ error: error.message || 'Failed to retrieve chat IDs' }); + } + } +}); + +router.post('/getAllMessagesInChat', async (req: Request, res: Response) => { + console.log('[routes/whatsappActions] POST /getAllMessagesInChat called'); + try { + const { args } = req.body; + const { chatId, includeMe, includeNotifications } = args || {}; + if (!chatId) { + return res.status(400).json({ error: 'Missing "chatId" in request body' }); + } + + const result = await whatsappClient.getAllMessagesInChat(openWaUrl!, { + chatId, + includeMe, + includeNotifications, + }); + res.json(result); + } catch (error: any) { + console.error('[routes/whatsappActions] Error in /getAllMessagesInChat:', error.message); + try { + const parsedError = JSON.parse(error.message.substring(error.message.indexOf('{'))); + return res.status(parsedError.status || 500).json({ error: parsedError }); + } catch { res.status(500).json({ error: error.message || 'Failed to retrieve chat messages' }); } } diff --git a/whatsapp-router/src/webhook.ts b/whatsapp-router/src/webhook.ts index 4449a78..dfb5f16 100644 --- a/whatsapp-router/src/webhook.ts +++ b/whatsapp-router/src/webhook.ts @@ -62,7 +62,7 @@ export function registerWebhookRoutes( if (chatId) { try { conv = await addMessageToConversation(chatId, message, openWaUrl); - console.log(`🔄 Updated conversation for ${chatId}`, conv); + console.log(`🔄 Updated conversation for ${chatId}`); } catch (err: any) { console.warn('Failed updating conversation:', err.message); diff --git a/whatsapp-router/src/whatsappClient.ts b/whatsapp-router/src/whatsappClient.ts index b217e10..cae96e8 100644 --- a/whatsapp-router/src/whatsappClient.ts +++ b/whatsapp-router/src/whatsappClient.ts @@ -80,19 +80,21 @@ export async function deleteChat( */ export async function sendImageMessage( openWaUrl: string, - to: string, - path: string, // Assuming 'path' is how OpenWA refers to the image URL/path - caption?: string + args: { + to: string; + file: string; + filename?: string; + caption?: string; + quotedMsgId?: string; + waitForId?: boolean; + hideTags?: boolean; + } ): Promise { try { - const args: { to: string; path: string; caption?: string } = { to, path }; - if (caption) { - args.caption = caption; - } const response = await axios.post(`${openWaUrl}/sendImage`, { args }); return response.data?.response || response.data; } catch (error: any) { - console.error(`[whatsappClient] Error sending image message to ${to}:`, error.message); + console.error(`[whatsappClient] Error sending image message to ${args.to}:`, error.message); if (axios.isAxiosError(error) && error.response) { console.error('[whatsappClient] Axios error details:', error.response.data); throw new Error(`whatsappClient API error (${openWaUrl}/sendImage): ${error.response.status} - ${JSON.stringify(error.response.data)}`); @@ -112,23 +114,22 @@ export async function sendImageMessage( */ export async function sendFileMessage( openWaUrl: string, - to: string, - path: string, // Assuming 'path' is how OpenWA refers to the file URL/path - filename?: string, - caption?: string + args: { + to: string; + file: string; + filename?: string; + caption?: string; + ptt?: boolean; + withoutPreview?: boolean; + viewOnce?: boolean; + hideTags?: boolean; + } ): Promise { try { - const args: { to: string; path: string; filename?: string; caption?: string } = { to, path }; - if (filename) { - args.filename = filename; - } - if (caption) { - args.caption = caption; - } const response = await axios.post(`${openWaUrl}/sendFile`, { args }); return response.data?.response || response.data; } catch (error: any) { - console.error(`[whatsappClient] Error sending file message to ${to}:`, error.message); + console.error(`[whatsappClient] Error sending file message to ${args.to}:`, error.message); if (axios.isAxiosError(error) && error.response) { console.error('[whatsappClient] Axios error details:', error.response.data); throw new Error(`whatsappClient API error (${openWaUrl}/sendFile): ${error.response.status} - ${JSON.stringify(error.response.data)}`); @@ -143,20 +144,20 @@ export async function sendFileMessage( * @param chatId The ID of the chat to retrieve. * @returns A promise that resolves to the API response containing chat details. */ -export async function getChatById( +export async function getChat( openWaUrl: string, - chatId: string -): Promise> { // Assuming Chat type from types.ts + contactId: string +): Promise> { try { - const response = await axios.post(`${openWaUrl}/getChatById`, { args: { chatId } }); + const response = await axios.post(`${openWaUrl}/getChat`, { args: { contactId } }); return response.data?.response || response.data; } catch (error: any) { - console.error(`[whatsappClient] Error retrieving chat ${chatId}:`, error.message); + console.error(`[whatsappClient] Error retrieving chat ${contactId}:`, error.message); if (axios.isAxiosError(error) && error.response) { console.error('[whatsappClient] Axios error details:', error.response.data); - throw new Error(`whatsappClient API error (${openWaUrl}/getChatById): ${error.response.status} - ${JSON.stringify(error.response.data)}`); + throw new Error(`whatsappClient API error (${openWaUrl}/getChat): ${error.response.status} - ${JSON.stringify(error.response.data)}`); } - throw new Error(`whatsappClient error (${openWaUrl}/getChatById): ${error.message}`); + throw new Error(`whatsappClient error (${openWaUrl}/getChat): ${error.message}`); } } @@ -284,11 +285,13 @@ export async function unblockContact( * @returns A promise that resolves to the API response containing all chats. */ export async function getAllChats( - openWaUrl: string -): Promise> { // Assuming an array of Chat objects + openWaUrl: string, + withNewMessageOnly?: boolean +): Promise> { try { - // This might be a GET request or a POST without args, depending on OpenWA - const response = await axios.post(`${openWaUrl}/getAllChats`); + const response = await axios.post(`${openWaUrl}/getAllChats`, { + args: withNewMessageOnly !== undefined ? { withNewMessageOnly } : {}, + }); return response.data?.response || response.data; } catch (error: any) { console.error(`[whatsappClient] Error retrieving all chats:`, error.message); @@ -300,6 +303,22 @@ export async function getAllChats( } } +export async function getAllChatIds( + openWaUrl: string +): Promise> { + try { + const response = await axios.post(`${openWaUrl}/getAllChatIds`); + return response.data?.response || response.data; + } catch (error: any) { + console.error(`[whatsappClient] Error retrieving chat ids:`, error.message); + if (axios.isAxiosError(error) && error.response) { + console.error('[whatsappClient] Axios error details:', error.response.data); + throw new Error(`whatsappClient API error (${openWaUrl}/getAllChatIds): ${error.response.status} - ${JSON.stringify(error.response.data)}`); + } + throw new Error(`whatsappClient error (${openWaUrl}/getAllChatIds): ${error.message}`); + } +} + /** * Retrieves messages for a specific chat. * Maps to OpenWA's /loadAndGetAllMessagesInChat endpoint. @@ -310,32 +329,74 @@ export async function getAllChats( * @param includeNotifications Optional flag to include notification messages. * @returns A promise that resolves to the API response containing messages. */ -export async function getChatMessages( +export async function getAllMessagesInChat( openWaUrl: string, - chatId: string, - limit?: number, - includeMe?: boolean, - includeNotifications?: boolean -): Promise> { // Corrected to WhatsAppMessage[] + args: { + chatId: string; + includeMe?: boolean; + includeNotifications?: boolean; + } +): Promise> { try { - const args: { - chatId: string; - limit?: number; - includeMe?: boolean; - includeNotifications?: boolean; - } = { chatId }; - if (limit !== undefined) args.limit = limit; - if (includeMe !== undefined) args.includeMe = includeMe; - if (includeNotifications !== undefined) args.includeNotifications = includeNotifications; - - const response = await axios.post(`${openWaUrl}/loadAndGetAllMessagesInChat`, { args }); + const response = await axios.post(`${openWaUrl}/getAllMessagesInChat`, { args }); return response.data?.response || response.data; } catch (error: any) { - console.error(`[whatsappClient] Error retrieving messages for chat ${chatId}:`, error.message); + console.error(`[whatsappClient] Error retrieving messages for chat ${args.chatId}:`, error.message); if (axios.isAxiosError(error) && error.response) { console.error('[whatsappClient] Axios error details:', error.response.data); - throw new Error(`whatsappClient API error (${openWaUrl}/loadAndGetAllMessagesInChat): ${error.response.status} - ${JSON.stringify(error.response.data)}`); + throw new Error(`whatsappClient API error (${openWaUrl}/getAllMessagesInChat): ${error.response.status} - ${JSON.stringify(error.response.data)}`); } - throw new Error(`whatsappClient error (${openWaUrl}/loadAndGetAllMessagesInChat): ${error.message}`); + throw new Error(`whatsappClient error (${openWaUrl}/getAllMessagesInChat): ${error.message}`); + } +} + +export async function deleteMessage( + openWaUrl: string, + args: { chatId: string; messageId: string; onlyLocal?: boolean } +): Promise { + try { + const response = await axios.post(`${openWaUrl}/deleteMessage`, { args }); + return response.data?.response || response.data; + } catch (error: any) { + console.error(`[whatsappClient] Error deleting message ${args.messageId}:`, error.message); + if (axios.isAxiosError(error) && error.response) { + console.error('[whatsappClient] Axios error details:', error.response.data); + throw new Error(`whatsappClient API error (${openWaUrl}/deleteMessage): ${error.response.status} - ${JSON.stringify(error.response.data)}`); + } + throw new Error(`whatsappClient error (${openWaUrl}/deleteMessage): ${error.message}`); + } +} + +export async function editMessage( + openWaUrl: string, + args: { messageId: string; text: string } +): Promise { + try { + const response = await axios.post(`${openWaUrl}/editMessage`, { args }); + return response.data?.response || response.data; + } catch (error: any) { + console.error(`[whatsappClient] Error editing message ${args.messageId}:`, error.message); + if (axios.isAxiosError(error) && error.response) { + console.error('[whatsappClient] Axios error details:', error.response.data); + throw new Error(`whatsappClient API error (${openWaUrl}/editMessage): ${error.response.status} - ${JSON.stringify(error.response.data)}`); + } + throw new Error(`whatsappClient error (${openWaUrl}/editMessage): ${error.message}`); + } +} + +export async function forwardMessages( + openWaUrl: string, + args: { to: string; messages: string[]; skipMyMessages?: boolean } +): Promise { + try { + const response = await axios.post(`${openWaUrl}/forwardMessages`, { args }); + return response.data?.response || response.data; + } catch (error: any) { + console.error(`[whatsappClient] Error forwarding messages to ${args.to}:`, error.message); + if (axios.isAxiosError(error) && error.response) { + console.error('[whatsappClient] Axios error details:', error.response.data); + throw new Error(`whatsappClient API error (${openWaUrl}/forwardMessages): ${error.response.status} - ${JSON.stringify(error.response.data)}`); + } + throw new Error(`whatsappClient error (${openWaUrl}/forwardMessages): ${error.message}`); } }