package main import ( "bytes" "encoding/json" "fmt" "html" "io" "net" "net/http" "net/mail" "net/smtp" "strings" "time" "gitea.mediatoday.ru/mt/notify" ) // Notifier — канал отправки уведомлений type Notifier interface { Send(data RequestData) error } // formatPlain собирает текстовое тело без обёртки. func formatPlain(d RequestData) string { var b strings.Builder fmt.Fprintf(&b, "[%s] %s %s%s\n", d.Time.Format("2006-01-02T15:04:05Z07:00"), d.Method, d.Host, d.URL) fmt.Fprintf(&b, "RemoteAddr: %s\n", d.RemoteAddr) b.WriteString("Headers:\n") for name, values := range d.Header { for _, v := range values { fmt.Fprintf(&b, " %s: %s\n", name, v) } } fmt.Fprintf(&b, "Body:\n%s\n", d.Body) b.WriteString("----\n") return b.String() } // formatMarkdown — обёртка в код-блок для Telegram/Pachca. func formatMarkdown(d RequestData) string { return "```\n" + formatPlain(d) + "```" } // formatHTML — обёртка в
 с экранированием для Email.
func formatHTML(d RequestData) string {
	return "
" + html.EscapeString(formatPlain(d)) + "
" } // --- Telegram --- type telegramNotifier struct { bot *notify.Bot chatID int64 } func (t *telegramNotifier) Send(d RequestData) error { _, err := t.bot.SendTextMessage(t.chatID, formatMarkdown(d)) return err } // --- Pachca (входящий вебхук) --- type pachcaNotifier struct { webhookURL string client *http.Client } func (p *pachcaNotifier) Send(d RequestData) error { payload, err := json.Marshal(map[string]string{"message": formatMarkdown(d)}) if err != nil { return err } resp, err := p.client.Post(p.webhookURL, "application/json", bytes.NewReader(payload)) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode < 200 || resp.StatusCode >= 300 { body, _ := io.ReadAll(resp.Body) return fmt.Errorf("pachca webhook: HTTP %d: %s", resp.StatusCode, string(body)) } return nil } // --- Email --- type emailNotifier struct { auth notify.SmtpAuth from mail.Address to []mail.Address subject string } func (e *emailNotifier) Send(d RequestData) error { return notify.SendEmailHTML(e.auth, formatHTML(d), e.subject, e.from, e.to...) } // BuildNotifiers собирает map каналов из конфига. Добавляет только заданные секции. func BuildNotifiers(cfg *Config) (map[string]Notifier, error) { notifiers := map[string]Notifier{} if cfg.Telegram != nil { disableIPV6 := true if cfg.Telegram.DisableIPV6 != nil { disableIPV6 = *cfg.Telegram.DisableIPV6 } bot, err := notify.NewTelegram(cfg.Telegram.Token, disableIPV6) if err != nil { return nil, fmt.Errorf("telegram: %w", err) } notifiers["telegram"] = &telegramNotifier{bot: bot, chatID: cfg.Telegram.GroupID} } if cfg.Pachca != nil { if cfg.Pachca.WebhookURL == "" { return nil, fmt.Errorf("pachca: не задан webhook_url") } notifiers["pachca"] = &pachcaNotifier{ webhookURL: cfg.Pachca.WebhookURL, client: &http.Client{Timeout: 30 * time.Second}, } } if cfg.Email != nil { host, _, err := net.SplitHostPort(cfg.Email.SMTPAddr) if err != nil { return nil, fmt.Errorf("email smtp_addr: %w", err) } from, err := mail.ParseAddress(cfg.Email.From) if err != nil { return nil, fmt.Errorf("email from: %w", err) } var to []mail.Address for _, addr := range cfg.Email.To { a, err := mail.ParseAddress(addr) if err != nil { return nil, fmt.Errorf("email to %q: %w", addr, err) } to = append(to, *a) } if len(to) == 0 { return nil, fmt.Errorf("email: не задан ни один получатель (to)") } subject := cfg.Email.Subject if subject == "" { subject = "HTTP Logger" } notifiers["email"] = &emailNotifier{ auth: notify.SmtpAuth{ Addr: cfg.Email.SMTPAddr, Auth: smtp.PlainAuth("", cfg.Email.Username, cfg.Email.Password, host), }, from: *from, to: to, subject: subject, } } return notifiers, nil }