package whatsapp import ( "context" "fmt" "sync" "git.warky.dev/wdevs/whatshooked/internal/config" "git.warky.dev/wdevs/whatshooked/internal/events" "git.warky.dev/wdevs/whatshooked/internal/logging" "git.warky.dev/wdevs/whatshooked/internal/whatsapp/businessapi" "git.warky.dev/wdevs/whatshooked/internal/whatsapp/whatsmeow" "go.mau.fi/whatsmeow/types" ) // Manager manages multiple WhatsApp client connections type Manager struct { clients map[string]Client mu sync.RWMutex eventBus *events.EventBus mediaConfig config.MediaConfig config *config.Config configPath string onConfigUpdate func(*config.Config) error } // NewManager creates a new WhatsApp manager func NewManager(eventBus *events.EventBus, mediaConfig config.MediaConfig, cfg *config.Config, configPath string, onConfigUpdate func(*config.Config) error) *Manager { return &Manager{ clients: make(map[string]Client), eventBus: eventBus, mediaConfig: mediaConfig, config: cfg, configPath: configPath, onConfigUpdate: onConfigUpdate, } } // Connect establishes a connection to a WhatsApp account using the appropriate client type func (m *Manager) Connect(ctx context.Context, cfg config.WhatsAppConfig) error { m.mu.Lock() defer m.mu.Unlock() if _, exists := m.clients[cfg.ID]; exists { return fmt.Errorf("client %s already connected", cfg.ID) } var client Client var err error // Factory pattern based on type switch cfg.Type { case "business-api": client, err = businessapi.NewClient(cfg, m.eventBus, m.mediaConfig) case "whatsmeow", "": client, err = whatsmeow.NewClient(cfg, m.eventBus, m.mediaConfig) default: return fmt.Errorf("unknown client type: %s", cfg.Type) } if err != nil { return fmt.Errorf("failed to create client: %w", err) } if err := client.Connect(ctx); err != nil { return fmt.Errorf("failed to connect: %w", err) } m.clients[cfg.ID] = client logging.Info("Client connected", "account_id", cfg.ID, "type", client.GetType()) return nil } // Disconnect disconnects a WhatsApp client func (m *Manager) Disconnect(id string) error { m.mu.Lock() defer m.mu.Unlock() client, exists := m.clients[id] if !exists { return fmt.Errorf("client %s not found", id) } if err := client.Disconnect(); err != nil { return fmt.Errorf("failed to disconnect: %w", err) } delete(m.clients, id) logging.Info("Client disconnected", "account_id", id) return nil } // DisconnectAll disconnects all WhatsApp clients func (m *Manager) DisconnectAll() { m.mu.Lock() defer m.mu.Unlock() for id, client := range m.clients { if err := client.Disconnect(); err != nil { logging.Error("Failed to disconnect client", "account_id", id, "error", err) } else { logging.Info("Client disconnected", "account_id", id) } } m.clients = make(map[string]Client) } // SendTextMessage sends a text message from a specific account func (m *Manager) SendTextMessage(ctx context.Context, accountID string, jid types.JID, text string) error { m.mu.RLock() client, exists := m.clients[accountID] m.mu.RUnlock() if !exists { return fmt.Errorf("client %s not found", accountID) } _, err := client.SendTextMessage(ctx, jid, text) return err } // SendImage sends an image message from a specific account func (m *Manager) SendImage(ctx context.Context, accountID string, jid types.JID, imageData []byte, mimeType string, caption string) error { m.mu.RLock() client, exists := m.clients[accountID] m.mu.RUnlock() if !exists { return fmt.Errorf("client %s not found", accountID) } _, err := client.SendImage(ctx, jid, imageData, mimeType, caption) return err } // SendVideo sends a video message from a specific account func (m *Manager) SendVideo(ctx context.Context, accountID string, jid types.JID, videoData []byte, mimeType string, caption string) error { m.mu.RLock() client, exists := m.clients[accountID] m.mu.RUnlock() if !exists { return fmt.Errorf("client %s not found", accountID) } _, err := client.SendVideo(ctx, jid, videoData, mimeType, caption) return err } // SendDocument sends a document message from a specific account func (m *Manager) SendDocument(ctx context.Context, accountID string, jid types.JID, documentData []byte, mimeType string, filename string, caption string) error { m.mu.RLock() client, exists := m.clients[accountID] m.mu.RUnlock() if !exists { return fmt.Errorf("client %s not found", accountID) } _, err := client.SendDocument(ctx, jid, documentData, mimeType, filename, caption) return err } // GetClient returns a client by ID func (m *Manager) GetClient(id string) (Client, bool) { m.mu.RLock() defer m.mu.RUnlock() client, exists := m.clients[id] return client, exists }