diff --git a/README.md b/README.md index a480ee1..1336004 100644 --- a/README.md +++ b/README.md @@ -69,14 +69,15 @@ curl -X POST "https://whatsapp.nucleoriofrio.com/api/instances" \ -d '{"name": "Mi WhatsApp"}' ``` -### Chats y Contactos +### Obtener Lista de Chats/Contactos -| Método | Endpoint | Descripción | -|--------|----------|-------------| -| GET | `/api/messages/:instanceId/chats` | Listar todos los chats | -| POST | `/api/messages/:instanceId/new-chat` | Crear nuevo chat | +``` +GET /api/messages/:instanceId/chats +``` -**Ejemplo - Listar chats:** +Retorna todos los chats (contactos individuales y grupos) de una instancia, ordenados por última actividad. + +**Ejemplo:** ```bash curl -X GET "https://whatsapp.nucleoriofrio.com/api/messages/{instanceId}/chats" \ -H "Authorization: Bearer " @@ -86,99 +87,411 @@ curl -X GET "https://whatsapp.nucleoriofrio.com/api/messages/{instanceId}/chats" ```json [ { - "id": "abc123", + "id": "chat_abc123", "jid": "5491155551234@s.whatsapp.net", "name": "Juan Pérez", "isGroup": false, "unreadCount": 2, - "lastMessageAt": "2025-01-15T10:30:00Z" + "lastMessageAt": "2025-01-15T10:30:00Z", + "lastMessage": "Hola, ¿cómo estás?", + "lastMessageType": "text" + }, + { + "id": "chat_xyz789", + "jid": "120363123456789012@g.us", + "name": "Grupo de Trabajo", + "isGroup": true, + "unreadCount": 0, + "lastMessageAt": "2025-01-15T09:00:00Z", + "lastMessage": "Reunión a las 3pm", + "lastMessageType": "text" } ] ``` -### Mensajes +**Campos de respuesta:** +| Campo | Tipo | Descripción | +|-------|------|-------------| +| `id` | string | ID interno del chat (usar en otros endpoints) | +| `jid` | string | JID de WhatsApp del contacto/grupo | +| `name` | string | Nombre del contacto o grupo | +| `isGroup` | boolean | `true` si es un grupo | +| `unreadCount` | number | Cantidad de mensajes no leídos | +| `lastMessageAt` | string | Fecha ISO del último mensaje | +| `lastMessage` | string | Contenido del último mensaje | +| `lastMessageType` | string | Tipo del último mensaje | -| Método | Endpoint | Descripción | -|--------|----------|-------------| -| GET | `/api/messages/:instanceId/:chatId` | Obtener mensajes del chat | -| POST | `/api/messages/send` | Enviar mensaje (simple) | -| POST | `/api/messages/:instanceId/:chatId/send` | Enviar mensaje (avanzado) | +--- -**Ejemplo - Obtener mensajes:** +### Obtener Mensajes de un Chat + +``` +GET /api/messages/:instanceId/:chatId +``` + +Obtiene los mensajes de un chat con soporte para paginación. + +**Parámetros de Query:** +| Parámetro | Tipo | Default | Descripción | +|-----------|------|---------|-------------| +| `limit` | number | 50 | Cantidad de mensajes (máx: 100) | +| `offset` | number | 0 | Saltar N mensajes | +| `before` | string | - | Timestamp ISO para scroll infinito | + +**Ejemplo - Obtener últimos 50 mensajes:** ```bash -curl -X GET "https://whatsapp.nucleoriofrio.com/api/messages/{instanceId}/{chatId}?limit=50" \ +curl -X GET "https://whatsapp.nucleoriofrio.com/api/messages/{instanceId}/{chatId}" \ -H "Authorization: Bearer " ``` -**Ejemplo - Enviar mensaje de texto:** +**Ejemplo - Paginación con limit/offset:** ```bash -curl -X POST "https://whatsapp.nucleoriofrio.com/api/messages/send" \ +curl -X GET "https://whatsapp.nucleoriofrio.com/api/messages/{instanceId}/{chatId}?limit=20&offset=40" \ + -H "Authorization: Bearer " +``` + +**Ejemplo - Scroll infinito (mensajes antes de fecha):** +```bash +curl -X GET "https://whatsapp.nucleoriofrio.com/api/messages/{instanceId}/{chatId}?limit=20&before=2025-01-15T10:00:00Z" \ + -H "Authorization: Bearer " +``` + +**Respuesta:** +```json +[ + { + "id": "msg_123", + "messageId": "3EB0A1B2C3D4E5F6", + "chatId": "chat_abc123", + "fromJid": "5491155551234@s.whatsapp.net", + "fromMe": false, + "type": "text", + "content": "Hola, ¿cómo estás?", + "caption": null, + "media": null, + "timestamp": "2025-01-15T10:30:00Z", + "status": "read", + "pushName": "Juan", + "isGroup": false + }, + { + "id": "msg_124", + "messageId": "3EB0A1B2C3D4E5F7", + "chatId": "chat_abc123", + "fromJid": "me", + "fromMe": true, + "type": "image", + "content": null, + "caption": "Mira esta foto", + "media": { + "mimetype": "image/jpeg", + "filesize": 245000, + "width": 1920, + "height": 1080, + "thumbnail": "base64..." + }, + "timestamp": "2025-01-15T10:31:00Z", + "status": "delivered", + "isGroup": false + } +] +``` + +**Campos de respuesta por tipo:** + +| Campo | Descripción | +|-------|-------------| +| `messageId` | ID de WhatsApp (usar para reaccionar o citar) | +| `fromMe` | `true` si lo envié yo | +| `type` | text, image, video, audio, document, sticker, contact, location, poll, event | +| `content` | Texto del mensaje | +| `caption` | Caption de media | +| `media` | Info de archivo (mimetype, filesize, thumbnail, etc.) | +| `poll` | Datos de encuesta (name, options, selectableCount) | +| `event` | Datos de evento (name, startDate, location) | +| `quoted` | Mensaje citado (id, content, type) | +| `pushName` | Nombre del remitente | +| `participant` | JID del participante (en grupos) | + +--- + +### Enviar Mensajes + +``` +POST /api/messages/:instanceId/:chatId/send +``` + +Endpoint unificado para enviar todos los tipos de mensaje. El tipo se detecta automáticamente según el Content-Type y body. + +#### Mensaje de Texto + +```bash +curl -X POST "https://whatsapp.nucleoriofrio.com/api/messages/{instanceId}/{chatId}/send" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ - "instanceId": "inst_abc123", - "to": "5491155551234", - "message": "Hola! Este es un mensaje de prueba" + "content": "Hola! Este es un mensaje de texto" }' ``` -**Ejemplo - Enviar imagen:** +**Con mensaje citado:** ```bash curl -X POST "https://whatsapp.nucleoriofrio.com/api/messages/{instanceId}/{chatId}/send" \ -H "Authorization: Bearer " \ - -F "file=@imagen.jpg" \ + -H "Content-Type: application/json" \ + -d '{ + "content": "Esta es mi respuesta", + "quotedMessageId": "3EB0A1B2C3D4E5F6" + }' +``` + +#### Imagen + +```bash +curl -X POST "https://whatsapp.nucleoriofrio.com/api/messages/{instanceId}/{chatId}/send" \ + -H "Authorization: Bearer " \ + -F "file=@foto.jpg" \ -F "caption=Descripción de la imagen" ``` -**Ejemplo - Enviar documento:** +#### Video + ```bash curl -X POST "https://whatsapp.nucleoriofrio.com/api/messages/{instanceId}/{chatId}/send" \ -H "Authorization: Bearer " \ - -F "file=@documento.pdf" \ - -F "caption=Reporte mensual" + -F "file=@video.mp4" \ + -F "caption=Mi video" ``` -**Ejemplo - Enviar contacto:** +#### Audio / Nota de Voz + +```bash +# Audio normal +curl -X POST "https://whatsapp.nucleoriofrio.com/api/messages/{instanceId}/{chatId}/send" \ + -H "Authorization: Bearer " \ + -F "file=@audio.mp3" + +# Nota de voz (PTT - Push To Talk) +curl -X POST "https://whatsapp.nucleoriofrio.com/api/messages/{instanceId}/{chatId}/send" \ + -H "Authorization: Bearer " \ + -F "file=@nota.ogg" \ + -F "isPtt=true" +``` + +#### Documento + +```bash +curl -X POST "https://whatsapp.nucleoriofrio.com/api/messages/{instanceId}/{chatId}/send" \ + -H "Authorization: Bearer " \ + -F "file=@reporte.pdf" \ + -F "caption=Reporte mensual de ventas" +``` + +#### Sticker + +```bash +curl -X POST "https://whatsapp.nucleoriofrio.com/api/messages/{instanceId}/{chatId}/send" \ + -H "Authorization: Bearer " \ + -F "file=@imagen.png" \ + -F 'asSticker=["true"]' +``` + +> Se convierte automáticamente a WebP. Formatos soportados: JPG, PNG, WebP. + +#### Contacto + ```bash curl -X POST "https://whatsapp.nucleoriofrio.com/api/messages/{instanceId}/{chatId}/send" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "type": "contact", - "contacts": [{ - "displayName": "Juan Pérez", - "phoneNumber": "+5491155551234" - }] + "contacts": [ + { + "displayName": "Juan Pérez", + "phoneNumber": "+5491155551234", + "organization": "Empresa SA" + } + ] }' ``` -**Ejemplo - Enviar encuesta:** +**Múltiples contactos:** +```bash +curl -X POST "https://whatsapp.nucleoriofrio.com/api/messages/{instanceId}/{chatId}/send" \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{ + "type": "contact", + "contacts": [ + {"displayName": "Juan", "phoneNumber": "+5491155551234"}, + {"displayName": "María", "phoneNumber": "+5491166662345"} + ] + }' +``` + +#### Encuesta (Poll) + ```bash curl -X POST "https://whatsapp.nucleoriofrio.com/api/messages/{instanceId}/{chatId}/send" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "type": "poll", - "name": "¿Qué día prefieres?", - "options": ["Lunes", "Martes", "Miércoles"], + "name": "¿Qué día prefieres para la reunión?", + "options": ["Lunes", "Martes", "Miércoles", "Jueves", "Viernes"], "selectableCount": 1 }' ``` -### Reacciones +**Parámetros de Poll:** +| Campo | Requerido | Descripción | +|-------|-----------|-------------| +| `name` | Sí | Pregunta de la encuesta | +| `options` | Sí | Array de opciones (mín: 2, máx: 12) | +| `selectableCount` | No | Cuántas opciones se pueden elegir (default: 1) | -**Ejemplo - Reaccionar a mensaje:** +#### Evento + +```bash +curl -X POST "https://whatsapp.nucleoriofrio.com/api/messages/{instanceId}/{chatId}/send" \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{ + "type": "event", + "name": "Reunión de equipo", + "startDate": "2025-01-20T14:00:00Z", + "endDate": "2025-01-20T15:00:00Z", + "description": "Revisión semanal del proyecto", + "location": { + "name": "Oficina Central", + "address": "Av. Corrientes 1234, CABA", + "latitude": -34.6037, + "longitude": -58.3816 + } + }' +``` + +**Parámetros de Event:** +| Campo | Requerido | Descripción | +|-------|-----------|-------------| +| `name` | Sí | Nombre del evento | +| `startDate` | Sí | Fecha/hora inicio (ISO 8601) | +| `endDate` | No | Fecha/hora fin | +| `description` | No | Descripción del evento | +| `location.name` | No | Nombre del lugar | +| `location.address` | No | Dirección | +| `location.latitude` | No | Latitud | +| `location.longitude` | No | Longitud | + +**Respuesta de envío (todos los tipos):** +```json +{ + "success": true, + "messages": [ + { + "messageId": "3EB0A1B2C3D4E5F6", + "type": "text" + } + ] +} +``` + +--- + +### Responder/Citar Mensajes (quotedMessageId) + +Todos los tipos de mensaje soportan citar otro mensaje usando `quotedMessageId`. El mensaje citado aparece como "respuesta a" en WhatsApp. + +**Texto citando otro mensaje:** +```bash +curl -X POST "https://whatsapp.nucleoriofrio.com/api/messages/{instanceId}/{chatId}/send" \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{ + "content": "Totalmente de acuerdo!", + "quotedMessageId": "3EB0A1B2C3D4E5F6" + }' +``` + +**Imagen citando otro mensaje:** +```bash +curl -X POST "https://whatsapp.nucleoriofrio.com/api/messages/{instanceId}/{chatId}/send" \ + -H "Authorization: Bearer " \ + -F "file=@imagen.jpg" \ + -F "caption=Mira esto!" \ + -F "quotedMessageId=3EB0A1B2C3D4E5F6" +``` + +**Encuesta citando otro mensaje:** +```bash +curl -X POST "https://whatsapp.nucleoriofrio.com/api/messages/{instanceId}/{chatId}/send" \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{ + "type": "poll", + "name": "¿Qué opinan sobre esto?", + "options": ["De acuerdo", "En desacuerdo", "Neutral"], + "quotedMessageId": "3EB0A1B2C3D4E5F6" + }' +``` + +> **Nota:** El `quotedMessageId` se obtiene del campo `messageId` al listar mensajes con `GET /api/messages/:instanceId/:chatId` + +--- + +### Reaccionar a Mensajes + +``` +POST /api/messages/:instanceId/react +``` + +Envía una reacción (emoji) a un mensaje existente. + +**Body:** +```json +{ + "messageId": "3EB0A1B2C3D4E5F6", + "emoji": "👍" +} +``` + +**Ejemplo - Agregar reacción:** ```bash curl -X POST "https://whatsapp.nucleoriofrio.com/api/messages/{instanceId}/react" \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ - -d '{ - "messageId": "3EB0A1B2C3D4E5F6", - "emoji": "👍" - }' + -d '{"messageId": "3EB0A1B2C3D4E5F6", "emoji": "👍"}' ``` -**Quitar reacción:** enviar `emoji` como string vacío `""` +**Ejemplo - Quitar reacción:** +```bash +curl -X POST "https://whatsapp.nucleoriofrio.com/api/messages/{instanceId}/react" \ + -H "Authorization: Bearer " \ + -H "Content-Type: application/json" \ + -d '{"messageId": "3EB0A1B2C3D4E5F6", "emoji": ""}' +``` + +**Emojis comunes:** +| Emoji | Significado | +|-------|-------------| +| 👍 | Me gusta | +| ❤️ | Amor | +| 😂 | Risa | +| 😮 | Sorpresa | +| 😢 | Tristeza | +| 🙏 | Gracias | + +**Respuesta:** +```json +{ + "success": true, + "messageId": "3EB0A1B2C3D4E5F6", + "emoji": "👍" +} +``` + +> **Nota:** El `messageId` se obtiene de la respuesta al obtener mensajes (`GET /api/messages/:instanceId/:chatId`) ### Formato de Destinatarios (JID) diff --git a/server/api/messages/[instanceId]/chats.get.ts b/server/api/messages/[instanceId]/chats.get.ts index c7eed2d..807a5ed 100644 --- a/server/api/messages/[instanceId]/chats.get.ts +++ b/server/api/messages/[instanceId]/chats.get.ts @@ -8,6 +8,7 @@ interface ChatRow { id: string jid: string name: string | null + alias: string | null is_group: boolean unread_count: number last_message_at: Date | null @@ -31,7 +32,7 @@ export default defineEventHandler(async (event) => { // Get chats with last message and type const result = await query( - `SELECT c.id, c.jid, c.name, c.is_group, c.unread_count, c.last_message_at, + `SELECT c.id, c.jid, c.name, c.alias, c.is_group, c.unread_count, c.last_message_at, c.last_message_type, (SELECT COALESCE(content, caption) FROM messages m WHERE m.chat_id = c.id ORDER BY timestamp DESC LIMIT 1) as last_message FROM chats c @@ -43,7 +44,11 @@ export default defineEventHandler(async (event) => { return result.rows.map(row => ({ id: row.id, jid: row.jid, - name: row.name || row.jid.split('@')[0], + // Alias has priority over name + name: row.alias || row.name || row.jid.split('@')[0], + // Keep original name for reference + originalName: row.name || row.jid.split('@')[0], + alias: row.alias, isGroup: row.is_group, unreadCount: row.unread_count, lastMessageAt: row.last_message_at,