package webservices import ( "encoding/json" "fmt" "html/template" "net/http" "stream-bot/internal/db" "stream-bot/internal/logger" "strings" ) type AlertService struct { *baseService config *db.AlertWebConfig server *http.Server } func NewAlertService(port int, config *db.AlertWebConfig) *AlertService { return &AlertService{ baseService: newBaseService(port), config: config, } } func (s *AlertService) Start() error { mux := http.NewServeMux() mux.HandleFunc("/", s.handleIndex) mux.HandleFunc("/events", s.handleEvents) mux.HandleFunc("/notify", s.handleNotify) // новый эндпоинт mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("data/media")))) mux.Handle("/sounds/", http.StripPrefix("/sounds/", http.FileServer(http.Dir("data/sounds")))) // для звуков s.server = &http.Server{Addr: fmt.Sprintf(":%d", s.port), Handler: mux} s.running = true go func() { if err := s.server.ListenAndServe(); nil != err && err != http.ErrServerClosed { logger.Error("Alert service error on port %d: %v", s.port, err) } }() logger.Info("Alert service started on port %d", s.port) return nil } // handleNotify принимает POST-запросы с уведомлениями func (s *AlertService) handleNotify(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return } var data map[string]interface{} if err := json.NewDecoder(r.Body).Decode(&data); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } // Отправляем всем подключённым клиентам s.broadcast(data) w.WriteHeader(http.StatusOK) } func (s *AlertService) Stop() error { s.running = false if s.server != nil { return s.server.Close() } return nil } func (s *AlertService) ReloadConfig(config interface{}) error { newCfg, ok := config.(*db.AlertWebConfig) if !ok { return fmt.Errorf("invalid config type, expected *db.AlertWebConfig") } s.config = newCfg return nil } func (s *AlertService) GetPort() int { return s.port } func (s *AlertService) GetType() string { return "alert" } func (s *AlertService) IsRunning() bool { return s.running } // SendToClients обрабатывает событие и рассылает его клиентам func (s *AlertService) SendToClients(event AlertEvent) { s.mu.RLock() cfg := s.config s.mu.RUnlock() if cfg == nil { return } // Обработка команды со звуком (без визуального уведомления) if event.Type == "command_sound" && event.Sound != "" { out := map[string]interface{}{ "title": "", "text": "", "duration": 1, "image": "", "sound": event.Sound, } s.broadcast(out) return } // Обычные события (follow, subscribe и т.д.) eventCfg, ok := cfg.Events[event.Type] if !ok || !eventCfg.Enabled { return } title := replacePlaceholders(eventCfg.TitleTemplate, event.Data) text := replacePlaceholders(eventCfg.TextTemplate, event.Data) duration := eventCfg.DurationSec if duration == 0 { duration = cfg.DefaultDuration } image := eventCfg.ImageFile if image == "" { image = cfg.DefaultImage } sound := eventCfg.SoundFile if sound == "" { sound = cfg.DefaultSound } out := map[string]interface{}{ "title": title, "text": text, "duration": duration, "image": image, "sound": sound, } s.broadcast(out) } func (s *AlertService) handleIndex(w http.ResponseWriter, _ *http.Request) { tmpl := `