Skip to main content
Glama
luiso2

Evolution API WhatsApp MCP Server

by luiso2
instance-manager.ts10.8 kB
import { EvolutionAPI } from './evolution-api.js'; import logger from '../utils/logger.js'; export interface WhatsAppInstance { name: string; status: 'connected' | 'disconnected' | 'connecting' | 'qr_code' | 'error'; qrCode?: string; lastSeen?: Date; phoneNumber?: string; profileName?: string; autoReply: boolean; webhookUrl?: string; } export class InstanceManager { private instances: Map<string, WhatsAppInstance> = new Map(); private evolutionAPI: EvolutionAPI; private statusCheckInterval: NodeJS.Timeout | null = null; private readonly CHECK_INTERVAL = 30000; // 30 segundos constructor(evolutionAPI: EvolutionAPI) { this.evolutionAPI = evolutionAPI; this.startStatusMonitoring(); } /** * Inicia el monitoreo automático del estado de las instancias */ private startStatusMonitoring(): void { logger.info('🔄 Starting instance status monitoring', { interval: `${this.CHECK_INTERVAL / 1000}s` }); this.statusCheckInterval = setInterval(async () => { await this.updateAllInstancesStatus(); }, this.CHECK_INTERVAL); } /** * Detiene el monitoreo de estado */ public stopStatusMonitoring(): void { if (this.statusCheckInterval) { clearInterval(this.statusCheckInterval); this.statusCheckInterval = null; logger.info('⏹️ Instance status monitoring stopped'); } } /** * Actualiza el estado de todas las instancias */ private async updateAllInstancesStatus(): Promise<void> { try { const evolutionInstances = await this.evolutionAPI.fetchInstances(); if (!evolutionInstances || !Array.isArray(evolutionInstances)) { logger.warn('⚠️ No instances found from Evolution API'); return; } logger.debug('🔍 Updating status for all instances', { count: evolutionInstances.length }); for (const evolutionInstance of evolutionInstances) { await this.updateInstanceStatus(evolutionInstance.instanceName, evolutionInstance); } // Marcar instancias que ya no existen en Evolution API como desconectadas for (const [instanceName, instance] of this.instances) { const exists = evolutionInstances.some(ei => ei.instanceName === instanceName); if (!exists && instance.status !== 'disconnected') { logger.warn('📱 Instance no longer exists in Evolution API', { instanceName }); this.updateLocalInstanceStatus(instanceName, 'disconnected'); } } } catch (error) { logger.error('❌ Failed to update instances status', { error: error instanceof Error ? error.message : 'Unknown error' }); } } /** * Actualiza el estado de una instancia específica */ private async updateInstanceStatus(instanceName: string, evolutionData?: any): Promise<void> { try { let status: WhatsAppInstance['status'] = 'disconnected'; let qrCode: string | undefined; let phoneNumber: string | undefined; let profileName: string | undefined; if (evolutionData) { // Usar datos ya obtenidos const connectionStatus = evolutionData.status || 'close'; status = this.mapEvolutionStatus(connectionStatus); phoneNumber = evolutionData.owner; profileName = evolutionData.profileName; } else { // Obtener estado específico de la instancia const connectionStatus = await this.evolutionAPI.getConnectionStatus(instanceName); status = this.mapEvolutionStatus(connectionStatus.state || 'close'); phoneNumber = undefined; // No disponible en connectionStatus profileName = undefined; // No disponible en connectionStatus } // Si la instancia necesita QR, usar el QR de evolutionData si está disponible if (status === 'qr_code' && evolutionData?.qrcode?.base64) { qrCode = evolutionData.qrcode.base64; } this.updateLocalInstanceStatus(instanceName, status, { qrCode, phoneNumber, profileName, lastSeen: new Date() }); } catch (error) { logger.error('❌ Failed to update instance status', { instanceName, error: error instanceof Error ? error.message : 'Unknown error' }); this.updateLocalInstanceStatus(instanceName, 'error'); } } /** * Mapea el estado de Evolution API a nuestro formato */ private mapEvolutionStatus(evolutionStatus: string): WhatsAppInstance['status'] { switch (evolutionStatus.toLowerCase()) { case 'open': case 'connected': return 'connected'; case 'connecting': return 'connecting'; case 'qr_code': case 'qr': return 'qr_code'; case 'close': case 'closed': case 'disconnected': return 'disconnected'; default: return 'error'; } } /** * Actualiza el estado local de una instancia */ private updateLocalInstanceStatus( instanceName: string, status: WhatsAppInstance['status'], additionalData?: Partial<WhatsAppInstance> ): void { const existingInstance = this.instances.get(instanceName); const updatedInstance: WhatsAppInstance = { name: instanceName, status, autoReply: existingInstance?.autoReply ?? true, lastSeen: new Date(), ...additionalData }; const previousStatus = existingInstance?.status; this.instances.set(instanceName, updatedInstance); // Log cambios de estado if (previousStatus && previousStatus !== status) { logger.info('📱 Instance status changed', { instanceName, previousStatus, newStatus: status, phoneNumber: updatedInstance.phoneNumber }); } } /** * Crea una nueva instancia de WhatsApp */ public async createInstance(instanceName: string, webhookUrl?: string): Promise<WhatsAppInstance> { try { logger.info('🆕 Creating new WhatsApp instance', { instanceName, webhookUrl }); await this.evolutionAPI.createInstance({ instanceName, webhookUrl, qrcode: true, webhookByEvents: true }); const newInstance: WhatsAppInstance = { name: instanceName, status: 'connecting', autoReply: true, webhookUrl, lastSeen: new Date() }; this.instances.set(instanceName, newInstance); // Actualizar estado inmediatamente setTimeout(() => { this.updateInstanceStatus(instanceName); }, 2000); logger.info('✅ Instance created successfully', { instanceName }); return newInstance; } catch (error) { logger.error('❌ Failed to create instance', { instanceName, error: error instanceof Error ? error.message : 'Unknown error' }); throw error; } } /** * Conecta una instancia existente */ public async connectInstance(instanceName: string): Promise<void> { try { logger.info('🔌 Connecting instance', { instanceName }); await this.evolutionAPI.connectInstance(instanceName); this.updateLocalInstanceStatus(instanceName, 'connecting'); // Actualizar estado después de un momento setTimeout(() => { this.updateInstanceStatus(instanceName); }, 3000); } catch (error) { logger.error('❌ Failed to connect instance', { instanceName, error: error instanceof Error ? error.message : 'Unknown error' }); this.updateLocalInstanceStatus(instanceName, 'error'); throw error; } } /** * Desconecta una instancia */ public async disconnectInstance(instanceName: string): Promise<void> { try { logger.info('🔌 Disconnecting instance', { instanceName }); await this.evolutionAPI.logoutInstance(instanceName); this.updateLocalInstanceStatus(instanceName, 'disconnected'); } catch (error) { logger.error('❌ Failed to disconnect instance', { instanceName, error: error instanceof Error ? error.message : 'Unknown error' }); throw error; } } /** * Elimina una instancia */ public async deleteInstance(instanceName: string): Promise<void> { try { logger.info('🗑️ Deleting instance', { instanceName }); await this.evolutionAPI.deleteInstance(instanceName); this.instances.delete(instanceName); logger.info('✅ Instance deleted successfully', { instanceName }); } catch (error) { logger.error('❌ Failed to delete instance', { instanceName, error: error instanceof Error ? error.message : 'Unknown error' }); throw error; } } /** * Obtiene todas las instancias */ public getAllInstances(): WhatsAppInstance[] { return Array.from(this.instances.values()); } /** * Obtiene una instancia específica */ public getInstance(instanceName: string): WhatsAppInstance | undefined { return this.instances.get(instanceName); } /** * Obtiene instancias por estado */ public getInstancesByStatus(status: WhatsAppInstance['status']): WhatsAppInstance[] { return this.getAllInstances().filter(instance => instance.status === status); } /** * Configura la respuesta automática para una instancia */ public setAutoReply(instanceName: string, enabled: boolean): void { const instance = this.instances.get(instanceName); if (instance) { instance.autoReply = enabled; logger.info('⚙️ Auto-reply setting updated', { instanceName, enabled }); } } /** * Fuerza la actualización del estado de una instancia */ public async refreshInstanceStatus(instanceName: string): Promise<void> { await this.updateInstanceStatus(instanceName); } /** * Obtiene estadísticas de las instancias */ public getInstanceStats(): { total: number; connected: number; disconnected: number; connecting: number; qrCode: number; error: number; } { const instances = this.getAllInstances(); return { total: instances.length, connected: instances.filter(i => i.status === 'connected').length, disconnected: instances.filter(i => i.status === 'disconnected').length, connecting: instances.filter(i => i.status === 'connecting').length, qrCode: instances.filter(i => i.status === 'qr_code').length, error: instances.filter(i => i.status === 'error').length }; } /** * Limpia recursos al cerrar */ public cleanup(): void { this.stopStatusMonitoring(); logger.info('🧹 Instance manager cleanup completed'); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/luiso2/mcp-evolution-api'

If you have feedback or need assistance with the MCP directory API, please join our Discord server