package events import ( "bytes" "encoding/json" "fmt" "net/http" "strings" "stream-bot/internal/audio" "stream-bot/internal/db" "stream-bot/internal/hotkey" "stream-bot/internal/logger" "stream-bot/internal/webservices" ) type Processor struct { sendMessageFunc func(platform, text string) error webSrvMgr *webservices.Manager } // NewProcessor создаёт обработчик событий с функцией отправки сообщений и менеджером веб-сервисов func NewProcessor(sendMessageFunc func(platform, text string) error, webSrvMgr *webservices.Manager) *Processor { return &Processor{ sendMessageFunc: sendMessageFunc, webSrvMgr: webSrvMgr, } } func (p *Processor) ProcessEvent(platform, eventName string, params map[string]string) { // 1. Выполняем действия, сохранённые в БД (send_message, play_sound, press_hotkey) actions, err := db.GetEventActions(platform, eventName) if err != nil { logger.Error("Failed to get event actions: %v", err) } else { for _, action := range actions { switch action.Type { case "send_message": if p.sendMessageFunc == nil { logger.Error("sendMessageFunc is nil, cannot send message") continue } text := action.Text for k, v := range params { placeholder := "{{" + k + "}}" text = strings.ReplaceAll(text, placeholder, v) } if err := p.sendMessageFunc(platform, text); err != nil { logger.Error("Send message error: %v", err) } case "play_sound": if err := audio.PlaySound(action.SoundFile); err != nil { logger.Error("Play sound error: %v", err) } case "press_hotkey": if err := hotkey.PressCombination(action.Keys); err != nil { logger.Error("Hotkey error: %v", err) } case "http_request": logger.Info("[Event] HTTP request to %s", action.URL) } } } // 2. Отправляем уведомление во все запущенные alert-сервисы (если есть) if p.webSrvMgr == nil { return } // Формируем заголовок и текст уведомления на основе типа события title, text := p.formatEventNotification(eventName, params) if title == "" && text == "" { // Если событие не требует уведомления, не отправляем return } // Получаем звук для события (можно настроить позже, пока используем стандартные) soundFile := p.getSoundForEvent(eventName) payload := map[string]interface{}{ "title": title, "text": text, "duration": 5, "image": "", "sound": soundFile, } // Отправляем во все alert-сервисы services := p.webSrvMgr.GetAllServices() for _, srv := range services { if srv.GetType() != "alert" { continue } port := srv.GetPort() url := fmt.Sprintf("http://localhost:%d/notify", port) body, _ := json.Marshal(payload) go func(url string, body []byte) { resp, err := http.Post(url, "application/json", bytes.NewReader(body)) if err != nil { logger.Error("Failed to send alert to service %s: %v", url, err) } else { resp.Body.Close() } }(url, body) } } // formatEventNotification формирует заголовок и текст уведомления для события func (p *Processor) formatEventNotification(eventName string, params map[string]string) (title, text string) { switch eventName { case "follow": username := params["username"] title = "Новый фолловер!" text = username + " теперь с нами" case "subscribe": username := params["username"] tier := params["tier"] tierName := "" switch tier { case "1000": tierName = "1 уровень" case "2000": tierName = "2 уровень" case "3000": tierName = "3 уровень" default: tierName = tier } title = "Спасибо за подписку!" text = username + " подписался на " + tierName case "gift_sub": gifter := params["gifter"] recipient := params["recipient"] total := params["cumulative_total"] title = "Подарочная подписка!" if recipient != "" { text = gifter + " подарил подписку для " + recipient } else { text = gifter + " подарил " + total + " подписок" } case "raid": from := params["from"] viewers := params["viewers"] title = "Рейд!" text = from + " привёл " + viewers + " зрителей" case "reward_redemption": username := params["username"] reward := params["reward_title"] title = "Активирована награда!" text = username + " активировал " + reward default: return "", "" } return title, text } // getSoundForEvent возвращает путь к звуковому файлу для события (можно вынести в настройки) func (p *Processor) getSoundForEvent(eventName string) string { // Здесь можно читать настройки из БД, пока используем заглушку sounds := map[string]string{ "follow": "/sounds/follow.mp3", "subscribe": "/sounds/sub.mp3", "gift_sub": "/sounds/gift.mp3", "raid": "/sounds/raid.mp3", "reward_redemption": "/sounds/reward.mp3", } if s, ok := sounds[eventName]; ok { return s } return "/sounds/default.mp3" }