Feature: Agregar botón para crear webhook de debug automáticamente
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m4s
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m4s
- Agregar botón "Crear Webhook de Debug" en WebhookReceiverSection - Detectar si ya existe un webhook apuntando al receptor de debug - Permitir eliminar el webhook de debug - Incluir todos los eventos disponibles al crear el webhook - También incluye mejoras previas de manejo de media y mensajes
This commit is contained in:
373
app/types/message.ts
Normal file
373
app/types/message.ts
Normal file
@@ -0,0 +1,373 @@
|
||||
/**
|
||||
* 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'
|
||||
| '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 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
|
||||
/** 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',
|
||||
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',
|
||||
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')}`
|
||||
}
|
||||
Reference in New Issue
Block a user