From 9c16fb55faef2e93d2b97b4a773795f7409cb3bb Mon Sep 17 00:00:00 2001 From: josedario87 <71241187+josedario87@users.noreply.github.com> Date: Sat, 7 Jun 2025 00:49:41 -0600 Subject: [PATCH] fix: align whatsapp routes with OpenWA docs --- whatsapp-router/src/routes/whatsappActions.ts | 231 +++++++++++++----- whatsapp-router/src/whatsappClient.ts | 163 ++++++++---- 2 files changed, 280 insertions(+), 114 deletions(-) diff --git a/whatsapp-router/src/routes/whatsappActions.ts b/whatsapp-router/src/routes/whatsappActions.ts index 7c11487..1d6981f 100644 --- a/whatsapp-router/src/routes/whatsappActions.ts +++ b/whatsapp-router/src/routes/whatsappActions.ts @@ -26,17 +26,18 @@ router.use((req: Request, res: Response, next: NextFunction) => { // Route implementations // POST /send-text -router.post('/send-text', async (req: Request, res: Response) => { +// POST /sendText +router.post('/sendText', async (req: Request, res: Response) => { 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); + console.error('[routes/whatsappActions] Error in /sendText:', error.message); // Check if the error message is already a JSON string from whatsappClient's error handling try { const parsedError = JSON.parse(error.message.substring(error.message.indexOf('{'))); @@ -48,18 +49,18 @@ router.post('/send-text', async (req: Request, res: Response) => { } }); -// DELETE /chats/:chatId -router.delete('/chats/:chatId', async (req: Request, res: Response) => { +// POST /deleteChat +router.post('/deleteChat', async (req: Request, res: Response) => { 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); 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('{'))); @@ -71,17 +72,97 @@ router.delete('/chats/:chatId', async (req: Request, res: Response) => { } }); -// POST /send-image -router.post('/send-image', async (req: Request, res: Response) => { +// POST /deleteMessage +router.post('/deleteMessage', async (req: Request, res: Response) => { 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) { + res.status(500).json({ error: error.message || 'Failed to delete message' }); + } + } +}); + +// POST /editMessage +router.post('/editMessage', async (req: Request, res: Response) => { + 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 (e) { + res.status(500).json({ error: error.message || 'Failed to edit message' }); + } + } +}); + +// POST /forwardMessages +router.post('/forwardMessages', async (req: Request, res: Response) => { + 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 (e) { + res.status(500).json({ error: error.message || 'Failed to forward messages' }); + } + } +}); + +// POST /sendImage +router.post('/sendImage', async (req: Request, res: Response) => { + 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 }); @@ -91,17 +172,36 @@ router.post('/send-image', async (req: Request, res: Response) => { } }); -// POST /send-file -router.post('/send-file', async (req: Request, res: Response) => { +// POST /sendFile +router.post('/sendFile', async (req: Request, res: Response) => { 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 }); @@ -111,17 +211,18 @@ router.post('/send-file', async (req: Request, res: Response) => { } }); -// GET /chats/:chatId -router.get('/chats/:chatId', async (req: Request, res: Response) => { +// POST /getChat +router.post('/getChat', async (req: Request, res: Response) => { 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 }); @@ -227,13 +328,15 @@ router.post('/blocklist/unblock', async (req: Request, res: Response) => { } }); -// GET /chats -router.get('/chats', async (req: Request, res: Response) => { +// POST /getAllChats +router.post('/getAllChats', async (req: Request, res: Response) => { try { - const result = await whatsappClient.getAllChats(openWaUrl!); + 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 /chats:', 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 }); @@ -243,37 +346,39 @@ router.get('/chats', async (req: Request, res: Response) => { } }); -// GET /chats/:chatId/messages -router.get('/chats/:chatId/messages', async (req: Request, res: Response) => { +// POST /getAllChatIds +router.post('/getAllChatIds', async (req: Request, res: Response) => { 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) { + res.status(500).json({ error: error.message || 'Failed to retrieve chat IDs' }); + } + } +}); + +// POST /getAllMessagesInChat +router.post('/getAllMessagesInChat', async (req: Request, res: Response) => { + 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 }); 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}`); } }