package alerts import ( "bytes" "encoding/json" "fmt" "io" "net/http" "net/smtp" "strings" "time" ) type NotifyConfig struct { TelegramBotToken string TelegramChatID string WebhookURL string EmailEnabled bool SMTPHost string SMTPPort int SMTPUser string SMTPPassword string EmailTo string EmailFrom string } func SendTelegram(cfg NotifyConfig, text string) error { if cfg.TelegramBotToken == "" || cfg.TelegramChatID == "" { return nil } url := fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", cfg.TelegramBotToken) payload, _ := json.Marshal(map[string]string{ "chat_id": cfg.TelegramChatID, "text": text, }) req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(payload)) if err != nil { return err } req.Header.Set("Content-Type", "application/json") client := &http.Client{Timeout: 15 * time.Second} resp, err := client.Do(req) if err != nil { return err } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) if resp.StatusCode >= 300 { msg := strings.TrimSpace(string(body)) if msg == "" { return fmt.Errorf("telegram API status %d", resp.StatusCode) } var apiErr struct { Description string `json:"description"` } if json.Unmarshal(body, &apiErr) == nil && apiErr.Description != "" { return fmt.Errorf("telegram: %s", apiErr.Description) } return fmt.Errorf("telegram API status %d: %s", resp.StatusCode, msg) } return nil } func SendEmail(cfg NotifyConfig, subject, body string) error { if !cfg.EmailEnabled || cfg.SMTPHost == "" || cfg.EmailTo == "" { return nil } from := cfg.EmailFrom if from == "" { from = cfg.SMTPUser } port := cfg.SMTPPort if port <= 0 { port = 587 } addr := fmt.Sprintf("%s:%d", cfg.SMTPHost, port) msg := strings.Join([]string{ fmt.Sprintf("From: %s", from), fmt.Sprintf("To: %s", cfg.EmailTo), fmt.Sprintf("Subject: %s", subject), "MIME-Version: 1.0", "Content-Type: text/plain; charset=UTF-8", "", body, }, "\r\n") var auth smtp.Auth if cfg.SMTPUser != "" { auth = smtp.PlainAuth("", cfg.SMTPUser, cfg.SMTPPassword, cfg.SMTPHost) } return smtp.SendMail(addr, auth, from, []string{cfg.EmailTo}, []byte(msg)) } func SendWebhook(cfg NotifyConfig, event, subject, text string) error { if cfg.WebhookURL == "" { return nil } payload, _ := json.Marshal(map[string]string{ "event": event, "title": subject, "message": text, }) req, err := http.NewRequest(http.MethodPost, cfg.WebhookURL, bytes.NewReader(payload)) if err != nil { return err } req.Header.Set("Content-Type", "application/json") client := &http.Client{Timeout: 15 * time.Second} resp, err := client.Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode >= 300 { body, _ := io.ReadAll(resp.Body) return fmt.Errorf("webhook status %d: %s", resp.StatusCode, strings.TrimSpace(string(body))) } return nil } func NotifyAll(cfg NotifyConfig, subject, text string) { _ = SendTelegram(cfg, subject+": "+text) _ = SendEmail(cfg, subject, text) } // NotifyAllEvent sends to Telegram, email, and optional operator webhook. func NotifyAllEvent(cfg NotifyConfig, event, subject, text string) { _ = SendTelegram(cfg, subject+": "+text) _ = SendEmail(cfg, subject, text) _ = SendWebhook(cfg, event, subject, text) }