All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m8s
- Backend: Nuevo soporte en endpoint /send para tipos contact, poll, event - UI: Modales para crear y enviar contactos, encuestas y eventos - Visualización: Componentes MessagePoll y MessageEvent para mostrar mensajes recibidos - Tipos: Agregar PollInfo, EventInfo y tipo 'event' a MessageType
425 lines
9.5 KiB
TypeScript
425 lines
9.5 KiB
TypeScript
/**
|
|
* Tipos centralizados para mensajes de WhatsApp
|
|
* Basado en la API de Baileys (@whiskeysockets/baileys)
|
|
*/
|
|
|
|
// Tipos de mensaje soportados por Baileys
|
|
export type MessageType =
|
|
| 'text'
|
|
| 'image'
|
|
| 'video'
|
|
| 'audio'
|
|
| 'document'
|
|
| 'sticker'
|
|
| 'contact'
|
|
| 'location'
|
|
| 'reaction'
|
|
| 'poll'
|
|
| 'event'
|
|
| 'unknown'
|
|
|
|
// Estados de mensaje
|
|
export type MessageStatus = 'pending' | 'sent' | 'delivered' | 'read' | 'failed'
|
|
|
|
// Estados de presencia
|
|
export type PresenceStatus = 'available' | 'unavailable' | 'composing' | 'recording' | 'paused'
|
|
|
|
/**
|
|
* Información de media (imagen, video, audio, documento, sticker)
|
|
*/
|
|
export interface MediaInfo {
|
|
/** URL del media (puede ser endpoint de API o URL directa) */
|
|
url?: string
|
|
/** Tipo MIME del archivo */
|
|
mimetype?: string
|
|
/** Nombre del archivo */
|
|
filename?: string
|
|
/** Tamaño en bytes */
|
|
filesize?: number
|
|
/** Ancho en píxeles (para imagen/video/sticker) */
|
|
width?: number
|
|
/** Alto en píxeles (para imagen/video/sticker) */
|
|
height?: number
|
|
/** Duración en segundos (para audio/video) */
|
|
duration?: number
|
|
/** Thumbnail en base64 */
|
|
thumbnail?: string
|
|
/** Si es mensaje de vista única */
|
|
isViewOnce?: boolean
|
|
/** Si es audio de voz (PTT) */
|
|
isPtt?: boolean
|
|
/** Waveform del audio (array de bytes) */
|
|
waveform?: number[]
|
|
}
|
|
|
|
/**
|
|
* Información de ubicación
|
|
*/
|
|
export interface LocationInfo {
|
|
/** Latitud */
|
|
latitude: number
|
|
/** Longitud */
|
|
longitude: number
|
|
/** Nombre del lugar */
|
|
name?: string
|
|
/** Dirección */
|
|
address?: string
|
|
/** URL del mapa */
|
|
url?: string
|
|
}
|
|
|
|
/**
|
|
* Información de contacto compartido
|
|
*/
|
|
export interface ContactInfo {
|
|
/** Nombre para mostrar */
|
|
displayName: string
|
|
/** vCard completo */
|
|
vcard: string
|
|
/** Números de teléfono extraídos */
|
|
phones?: string[]
|
|
}
|
|
|
|
/**
|
|
* Información de encuesta
|
|
*/
|
|
export interface PollInfo {
|
|
/** Nombre/pregunta de la encuesta */
|
|
name: string
|
|
/** Opciones de la encuesta */
|
|
options: string[]
|
|
/** Votos por opción */
|
|
votes?: number[]
|
|
/** Cantidad máxima de selecciones permitidas */
|
|
selectableCount?: number
|
|
}
|
|
|
|
/**
|
|
* Información de ubicación de evento
|
|
*/
|
|
export interface EventLocationInfo {
|
|
/** Nombre del lugar */
|
|
name?: string
|
|
/** Dirección */
|
|
address?: string
|
|
/** Latitud */
|
|
latitude?: number
|
|
/** Longitud */
|
|
longitude?: number
|
|
}
|
|
|
|
/**
|
|
* Información de evento
|
|
*/
|
|
export interface EventInfo {
|
|
/** Nombre del evento */
|
|
name: string
|
|
/** Fecha y hora de inicio */
|
|
startDate: string
|
|
/** Fecha y hora de fin */
|
|
endDate?: string
|
|
/** Descripción del evento */
|
|
description?: string
|
|
/** Ubicación del evento */
|
|
location?: EventLocationInfo
|
|
}
|
|
|
|
/**
|
|
* Información de mensaje citado (quoted/reply)
|
|
*/
|
|
export interface QuotedMessage {
|
|
/** ID del mensaje original */
|
|
id: string
|
|
/** Contenido del mensaje original */
|
|
content: string | null
|
|
/** Tipo del mensaje original */
|
|
type: MessageType
|
|
/** Si fue enviado por mí */
|
|
fromMe: boolean
|
|
/** JID del autor original (en grupos) */
|
|
participant?: string
|
|
/** Nombre del autor original */
|
|
participantName?: string
|
|
/** Media info si era un mensaje con media */
|
|
media?: MediaInfo
|
|
}
|
|
|
|
/**
|
|
* Información de reacción a un mensaje
|
|
*/
|
|
export interface ReactionInfo {
|
|
/** Emoji de la reacción */
|
|
emoji: string
|
|
/** JID de quien reaccionó */
|
|
reactorJid: string
|
|
/** Nombre de quien reaccionó */
|
|
reactorName?: string
|
|
/** Timestamp de la reacción */
|
|
timestamp?: Date
|
|
}
|
|
|
|
/**
|
|
* Mensaje completo con toda la información
|
|
*/
|
|
export interface Message {
|
|
/** UUID interno de la BD */
|
|
id: string
|
|
/** ID del mensaje de WhatsApp */
|
|
messageId: string
|
|
/** ID del chat */
|
|
chatId?: string
|
|
/** JID del remitente */
|
|
fromJid: string
|
|
/** Si fue enviado por mí */
|
|
fromMe: boolean
|
|
/** Tipo de mensaje */
|
|
type: MessageType
|
|
/** Contenido de texto (para text/caption) */
|
|
content: string | null
|
|
/** Caption para media */
|
|
caption?: string
|
|
/** Información de media */
|
|
media?: MediaInfo
|
|
/** Información de ubicación */
|
|
location?: LocationInfo
|
|
/** Información de contacto */
|
|
contact?: ContactInfo
|
|
/** Información de encuesta */
|
|
poll?: PollInfo
|
|
/** Información de evento */
|
|
event?: EventInfo
|
|
/** Mensaje citado */
|
|
quoted?: QuotedMessage
|
|
/** Reacciones al mensaje */
|
|
reactions?: ReactionInfo[]
|
|
/** Timestamp del mensaje */
|
|
timestamp: Date
|
|
/** Estado del mensaje */
|
|
status: MessageStatus
|
|
/** JID del participante en grupos */
|
|
participant?: string
|
|
/** Nombre del remitente (pushName) */
|
|
pushName?: string
|
|
/** Si es un mensaje del sistema (stub) */
|
|
isStub?: boolean
|
|
/** Tipo de stub (si aplica) */
|
|
stubType?: string
|
|
/** Parámetros del stub */
|
|
stubParams?: string[]
|
|
}
|
|
|
|
/**
|
|
* Información de presencia de un usuario
|
|
*/
|
|
export interface PresenceInfo {
|
|
/** Estado actual */
|
|
status: PresenceStatus
|
|
/** Última vez visto */
|
|
lastSeen?: Date
|
|
}
|
|
|
|
/**
|
|
* Participante de grupo
|
|
*/
|
|
export interface GroupParticipant {
|
|
/** JID del participante */
|
|
jid: string
|
|
/** Nombre del participante */
|
|
name?: string
|
|
/** Si es administrador */
|
|
isAdmin: boolean
|
|
/** Si es super administrador (creador) */
|
|
isSuperAdmin: boolean
|
|
}
|
|
|
|
/**
|
|
* Chat/Conversación
|
|
*/
|
|
export interface Chat {
|
|
/** UUID interno */
|
|
id: string
|
|
/** JID del chat */
|
|
jid: string
|
|
/** Nombre del chat */
|
|
name: string
|
|
/** Si es grupo */
|
|
isGroup: boolean
|
|
/** URL de foto de perfil */
|
|
profilePicture?: string
|
|
/** Último mensaje (texto o placeholder) */
|
|
lastMessage: string
|
|
/** Tipo del último mensaje */
|
|
lastMessageType?: MessageType
|
|
/** Timestamp del último mensaje */
|
|
lastMessageAt: Date
|
|
/** Contador de mensajes no leídos */
|
|
unreadCount: number
|
|
/** Presencia actual (si está suscrito) */
|
|
presence?: PresenceInfo
|
|
/** Participantes (solo para grupos) */
|
|
participants?: GroupParticipant[]
|
|
/** Si está archivado */
|
|
isArchived?: boolean
|
|
/** Si está fijado */
|
|
isPinned?: boolean
|
|
/** Si está silenciado */
|
|
isMuted?: boolean
|
|
}
|
|
|
|
/**
|
|
* Contenido para enviar un mensaje
|
|
*/
|
|
export interface SendMessageContent {
|
|
/** Texto del mensaje */
|
|
text?: string
|
|
/** Archivo de imagen */
|
|
image?: File | string
|
|
/** Archivo de video */
|
|
video?: File | string
|
|
/** Archivo de audio */
|
|
audio?: File | string
|
|
/** Archivo de documento */
|
|
document?: File | string
|
|
/** Caption para media */
|
|
caption?: string
|
|
/** ID del mensaje a citar */
|
|
quotedMessageId?: string
|
|
/** JIDs para mencionar */
|
|
mentions?: string[]
|
|
/** Si es audio PTT (nota de voz) */
|
|
isPtt?: boolean
|
|
}
|
|
|
|
/**
|
|
* Evento de actualización de presencia
|
|
*/
|
|
export interface PresenceUpdateEvent {
|
|
/** ID de la instancia */
|
|
instanceId: string
|
|
/** JID del chat */
|
|
jid: string
|
|
/** Presencias por participante */
|
|
presences: Record<string, PresenceInfo>
|
|
}
|
|
|
|
/**
|
|
* Evento de actualización de estado de mensaje
|
|
*/
|
|
export interface MessageStatusEvent {
|
|
/** ID de la instancia */
|
|
instanceId: string
|
|
/** ID del mensaje */
|
|
messageId: string
|
|
/** Nuevo estado */
|
|
status: MessageStatus
|
|
}
|
|
|
|
/**
|
|
* Evento de nuevo mensaje
|
|
*/
|
|
export interface MessageReceivedEvent {
|
|
/** ID de la instancia */
|
|
instanceId: string
|
|
/** Mensaje completo */
|
|
message: Message
|
|
}
|
|
|
|
/**
|
|
* Evento de reacción a mensaje
|
|
*/
|
|
export interface MessageReactionEvent {
|
|
/** ID de la instancia */
|
|
instanceId: string
|
|
/** ID del mensaje */
|
|
messageId: string
|
|
/** Información de la reacción */
|
|
reaction: ReactionInfo
|
|
}
|
|
|
|
// Utilidades
|
|
|
|
/**
|
|
* Obtiene el placeholder de texto para un tipo de mensaje
|
|
*/
|
|
export function getMessageTypePlaceholder(type: MessageType): string {
|
|
const placeholders: Record<MessageType, string> = {
|
|
text: '',
|
|
image: 'Foto',
|
|
video: 'Video',
|
|
audio: 'Audio',
|
|
document: 'Documento',
|
|
sticker: 'Sticker',
|
|
contact: 'Contacto',
|
|
location: 'Ubicación',
|
|
reaction: 'Reacción',
|
|
poll: 'Encuesta',
|
|
event: 'Evento',
|
|
unknown: 'Mensaje'
|
|
}
|
|
return placeholders[type] || 'Mensaje'
|
|
}
|
|
|
|
/**
|
|
* Obtiene el ícono para un tipo de mensaje
|
|
*/
|
|
export function getMessageTypeIcon(type: MessageType): string {
|
|
const icons: Record<MessageType, string> = {
|
|
text: 'i-lucide-message-square',
|
|
image: 'i-lucide-image',
|
|
video: 'i-lucide-video',
|
|
audio: 'i-lucide-music',
|
|
document: 'i-lucide-file',
|
|
sticker: 'i-lucide-smile',
|
|
contact: 'i-lucide-user',
|
|
location: 'i-lucide-map-pin',
|
|
reaction: 'i-lucide-heart',
|
|
poll: 'i-lucide-bar-chart',
|
|
event: 'i-lucide-calendar',
|
|
unknown: 'i-lucide-help-circle'
|
|
}
|
|
return icons[type] || 'i-lucide-message-square'
|
|
}
|
|
|
|
/**
|
|
* Genera un color consistente basado en un string (para nombres en grupos)
|
|
*/
|
|
export function stringToColor(str: string): string {
|
|
const colors = [
|
|
'#e17076', // rojo
|
|
'#faa774', // naranja
|
|
'#a695e7', // morado
|
|
'#7bc862', // verde
|
|
'#6ec9cb', // cyan
|
|
'#65aadd', // azul
|
|
'#ee7aae', // rosa
|
|
'#e5a36b', // marrón
|
|
]
|
|
|
|
let hash = 0
|
|
for (let i = 0; i < str.length; i++) {
|
|
hash = str.charCodeAt(i) + ((hash << 5) - hash)
|
|
}
|
|
|
|
return colors[Math.abs(hash) % colors.length]
|
|
}
|
|
|
|
/**
|
|
* Formatea bytes a tamaño legible
|
|
*/
|
|
export function formatFileSize(bytes: number): string {
|
|
if (bytes === 0) return '0 B'
|
|
const k = 1024
|
|
const sizes = ['B', 'KB', 'MB', 'GB']
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
|
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`
|
|
}
|
|
|
|
/**
|
|
* Formatea duración en segundos a mm:ss
|
|
*/
|
|
export function formatDuration(seconds: number): string {
|
|
const mins = Math.floor(seconds / 60)
|
|
const secs = Math.floor(seconds % 60)
|
|
return `${mins}:${secs.toString().padStart(2, '0')}`
|
|
}
|