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
}