feat: настраиваемые каналы уведомлений (telegram/pachca/email) с маршрутизацией по пути

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Vladimir V Maksimov
2026-06-14 11:10:44 +03:00
parent cfc9e23697
commit a21bb73175
6 changed files with 413 additions and 96 deletions

104
main.go
View File

@@ -1,117 +1,30 @@
package main
import (
"fmt"
"io"
"log"
"net"
"net/http"
"strings"
"time"
"gitea.mediatoday.ru/mt/notify"
)
func GetRemoteAddr(r *http.Request) string {
// Сначала смотрим X-Forwarded-For
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
// X-Forwarded-For может содержать несколько IP через запятую
ips := strings.Split(xff, ",")
if len(ips) > 0 {
return strings.TrimSpace(ips[0])
}
}
// Потом X-Real-IP
if realIP := r.Header.Get("X-Real-IP"); realIP != "" {
return realIP
}
// И в конце RemoteAddr
host, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
return r.RemoteAddr
}
return host
}
func BuildMessage(r *http.Request) string {
t := time.Now()
var body []byte
if r.ContentLength > 1024 {
body = []byte("too long")
} else {
// Читаем тело запроса
body, _ = io.ReadAll(r.Body)
_ = r.Body.Close()
}
// Формируем содержимое
entry := fmt.Sprintf("[%s] %s %s%s\n", t.Format(time.RFC3339), r.Method, r.Host, r.URL.String())
entry += "RemoteAddr: " + GetRemoteAddr(r) + "\n"
entry += "Headers:\n"
for name, values := range r.Header {
for _, v := range values {
entry += fmt.Sprintf(" %s: %s\n", name, v)
}
}
entry += fmt.Sprintf("Body:\n%s\n", string(body))
entry += "----\n"
entry = "```" + entry + "```"
return entry
}
func main() {
log.Println("Запуск приложения")
// Загружаем конфиг
cfg, err := loadConfig("config.yaml")
if err != nil {
log.Fatalf("Ошибка загрузки конфига: %v", err)
}
if cfg.Telegram == nil {
log.Fatalf("не указана секция telegram")
}
tg, err := notify.NewTelegram(cfg.Telegram.Token, true)
notifiers, err := BuildNotifiers(cfg)
if err != nil {
log.Fatal(err)
log.Fatalf("Ошибка инициализации каналов: %v", err)
}
if len(notifiers) == 0 {
log.Fatal("не сконфигурирован ни один канал уведомлений")
}
// Общий обработчик
handler := func(w http.ResponseWriter, r *http.Request) {
paths := strings.Split(r.URL.Path, "/")
switch {
case len(paths) < 2:
w.WriteHeader(http.StatusNotFound)
return
case paths[1] == "telegram":
default:
w.WriteHeader(http.StatusNotFound)
return
}
http.HandleFunc("/", makeHandler(notifiers))
entry := BuildMessage(r)
_, err = tg.SendTextMessage(cfg.Telegram.GroupID, entry)
if err != nil {
log.Println(err)
w.WriteHeader(http.StatusBadGateway)
w.Write([]byte(err.Error()))
} else {
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("OK"))
}
}
// Регистрируем хендлер
http.HandleFunc("/", handler)
// Запускаем серверы для всех адресов
for _, addr := range cfg.ListenAddresses {
addr := addr // захватываем в область видимости
addr := addr
go func() {
log.Printf("Слушаю %s ...", addr)
if err := http.ListenAndServe(addr, nil); err != nil {
@@ -120,6 +33,5 @@ func main() {
}()
}
// Блокируем main, чтобы программа не завершилась
select {}
}