Fix: Preservar alias al recargar lista de chats
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m9s
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m9s
El endpoint chats.get no retornaba el campo alias, lo que causaba que al recargar los chats (cuando llega un mensaje nuevo) el alias se perdiera. Cambios: - Agregar alias a la query de chats.get - Priorizar alias sobre name en la respuesta - Incluir originalName para referencia en el modal
This commit is contained in:
395
README.md
395
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 <API_KEY>"
|
||||
@@ -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 <API_KEY>"
|
||||
```
|
||||
|
||||
**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 <API_KEY>"
|
||||
```
|
||||
|
||||
**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 <API_KEY>"
|
||||
```
|
||||
|
||||
**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 <API_KEY>" \
|
||||
-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 <API_KEY>" \
|
||||
-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 <API_KEY>" \
|
||||
-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 <API_KEY>" \
|
||||
-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 <API_KEY>" \
|
||||
-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 <API_KEY>" \
|
||||
-F "file=@nota.ogg" \
|
||||
-F "isPtt=true"
|
||||
```
|
||||
|
||||
#### Documento
|
||||
|
||||
```bash
|
||||
curl -X POST "https://whatsapp.nucleoriofrio.com/api/messages/{instanceId}/{chatId}/send" \
|
||||
-H "Authorization: Bearer <API_KEY>" \
|
||||
-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 <API_KEY>" \
|
||||
-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 <API_KEY>" \
|
||||
-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 <API_KEY>" \
|
||||
-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 <API_KEY>" \
|
||||
-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 <API_KEY>" \
|
||||
-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 <API_KEY>" \
|
||||
-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 <API_KEY>" \
|
||||
-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 <API_KEY>" \
|
||||
-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 <API_KEY>" \
|
||||
-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 <API_KEY>" \
|
||||
-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)
|
||||
|
||||
|
||||
@@ -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<ChatRow>(
|
||||
`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,
|
||||
|
||||
Reference in New Issue
Block a user