Added mattermost alert
This commit is contained in:
parent
bc57f96b70
commit
9ee043ab15
4 changed files with 194 additions and 6 deletions
48
config.go
48
config.go
|
|
@ -23,8 +23,16 @@ const (
|
||||||
|
|
||||||
// --- Telegram ---
|
// --- Telegram ---
|
||||||
var (
|
var (
|
||||||
cfgTelegramBotToken = ""
|
cfgTelegramBotToken = ""
|
||||||
cfgTelegramChatID = ""
|
cfgTelegramChatID = ""
|
||||||
|
cfgTelegramSocks5Proxy = "" // Опционально: host:port
|
||||||
|
cfgTelegramSocks5User = "" // Логин прокси (опционально)
|
||||||
|
cfgTelegramSocks5Password = "" // Пароль прокси (опционально)
|
||||||
|
)
|
||||||
|
|
||||||
|
// --- Mattermost ---
|
||||||
|
var (
|
||||||
|
cfgMattermostWebhookURL = "" // Incoming Webhook URL
|
||||||
)
|
)
|
||||||
|
|
||||||
// --- Email / SMTP ---
|
// --- Email / SMTP ---
|
||||||
|
|
@ -152,6 +160,15 @@ func applyConfig(env map[string]string) {
|
||||||
if v, ok := env["TELEGRAM_CHAT_ID"]; ok && v != "" {
|
if v, ok := env["TELEGRAM_CHAT_ID"]; ok && v != "" {
|
||||||
cfgTelegramChatID = v
|
cfgTelegramChatID = v
|
||||||
}
|
}
|
||||||
|
if v, ok := env["TELEGRAM_SOCKS5_PROXY"]; ok && v != "" {
|
||||||
|
cfgTelegramSocks5Proxy = v
|
||||||
|
}
|
||||||
|
if v, ok := env["TELEGRAM_SOCKS5_USER"]; ok && v != "" {
|
||||||
|
cfgTelegramSocks5User = v
|
||||||
|
}
|
||||||
|
if v, ok := env["TELEGRAM_SOCKS5_PASSWORD"]; ok && v != "" {
|
||||||
|
cfgTelegramSocks5Password = v
|
||||||
|
}
|
||||||
|
|
||||||
// Database
|
// Database
|
||||||
if v, ok := env["DB_USER"]; ok && v != "" {
|
if v, ok := env["DB_USER"]; ok && v != "" {
|
||||||
|
|
@ -276,6 +293,12 @@ func applyConfig(env map[string]string) {
|
||||||
if v, ok := env["EMAIL_FROM"]; ok && v != "" {
|
if v, ok := env["EMAIL_FROM"]; ok && v != "" {
|
||||||
cfgEmailFrom = v
|
cfgEmailFrom = v
|
||||||
}
|
}
|
||||||
|
// Mattermost
|
||||||
|
if v, ok := env["MATTERMOST_WEBHOOK_URL"]; ok && v != "" {
|
||||||
|
cfgMattermostWebhookURL = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Email / SMTP
|
||||||
if v, ok := env["EMAIL_TO"]; ok && v != "" {
|
if v, ok := env["EMAIL_TO"]; ok && v != "" {
|
||||||
parts := strings.Split(v, ",")
|
parts := strings.Split(v, ",")
|
||||||
recipients := make([]string, 0, len(parts))
|
recipients := make([]string, 0, len(parts))
|
||||||
|
|
@ -300,6 +323,10 @@ func isTelegramConfigured() bool {
|
||||||
cfgTelegramChatID != "YOUR_CHAT_ID"
|
cfgTelegramChatID != "YOUR_CHAT_ID"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isMattermostConfigured() bool {
|
||||||
|
return cfgMattermostWebhookURL != ""
|
||||||
|
}
|
||||||
|
|
||||||
func isEmailConfigured() bool {
|
func isEmailConfigured() bool {
|
||||||
return cfgEmailSMTPHost != "" &&
|
return cfgEmailSMTPHost != "" &&
|
||||||
cfgEmailFrom != "" &&
|
cfgEmailFrom != "" &&
|
||||||
|
|
@ -312,7 +339,15 @@ func printConfigStatus() {
|
||||||
|
|
||||||
// Telegram
|
// Telegram
|
||||||
if isTelegramConfigured() {
|
if isTelegramConfigured() {
|
||||||
fmt.Println(" ✅ Telegram: настроен")
|
if cfgTelegramSocks5Proxy != "" {
|
||||||
|
if cfgTelegramSocks5User != "" {
|
||||||
|
fmt.Printf(" ✅ Telegram: настроен (прокси: %s, пользователь: %s)\n", cfgTelegramSocks5Proxy, cfgTelegramSocks5User)
|
||||||
|
} else {
|
||||||
|
fmt.Printf(" ✅ Telegram: настроен (прокси: %s, без авторизации)\n", cfgTelegramSocks5Proxy)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Println(" ✅ Telegram: настроен (прямое соединение)")
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
fmt.Println(" ⚠️ Telegram: не настроен")
|
fmt.Println(" ⚠️ Telegram: не настроен")
|
||||||
}
|
}
|
||||||
|
|
@ -324,6 +359,13 @@ func printConfigStatus() {
|
||||||
fmt.Println(" ⚠️ База данных: не настроена")
|
fmt.Println(" ⚠️ База данных: не настроена")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mattermost
|
||||||
|
if isMattermostConfigured() {
|
||||||
|
fmt.Printf(" ✅ Mattermost: настроен (webhook: %s)\n", cfgMattermostWebhookURL)
|
||||||
|
} else {
|
||||||
|
fmt.Println(" ⚠️ Mattermost: не настроен (MATTERMOST_WEBHOOK_URL)")
|
||||||
|
}
|
||||||
|
|
||||||
// Email
|
// Email
|
||||||
if isEmailConfigured() {
|
if isEmailConfigured() {
|
||||||
fmt.Printf(" ✅ Email: %s → %s (smtp: %s:%d)\n", cfgEmailFrom, strings.Join(cfgEmailTo, ", "), cfgEmailSMTPHost, cfgEmailSMTPPort)
|
fmt.Printf(" ✅ Email: %s → %s (smtp: %s:%d)\n", cfgEmailFrom, strings.Join(cfgEmailTo, ", "), cfgEmailSMTPHost, cfgEmailSMTPPort)
|
||||||
|
|
|
||||||
3
go.mod
3
go.mod
|
|
@ -7,6 +7,7 @@ require (
|
||||||
github.com/charmbracelet/bubbletea v1.3.10
|
github.com/charmbracelet/bubbletea v1.3.10
|
||||||
github.com/charmbracelet/lipgloss v1.1.0
|
github.com/charmbracelet/lipgloss v1.1.0
|
||||||
github.com/lib/pq v1.10.9
|
github.com/lib/pq v1.10.9
|
||||||
|
golang.org/x/net v0.38.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|
@ -31,4 +32,4 @@ require (
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||||
golang.org/x/sys v0.38.0 // indirect
|
golang.org/x/sys v0.38.0 // indirect
|
||||||
golang.org/x/text v0.3.8 // indirect
|
golang.org/x/text v0.3.8 // indirect
|
||||||
)
|
)
|
||||||
91
mattermost.go
Normal file
91
mattermost.go
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// mattermostSend отправляет сообщение в Mattermost через Incoming Webhook.
|
||||||
|
// HTML-теги из Telegram автоматически конвертируются в Markdown.
|
||||||
|
// Если вебхук не настроен — пишет в лог и не паникует.
|
||||||
|
func mattermostSend(message string) error {
|
||||||
|
if !isMattermostConfigured() {
|
||||||
|
log.Printf("[Mattermost] Не настроен (нужен MATTERMOST_WEBHOOK_URL) — пропускаем")
|
||||||
|
return fmt.Errorf("mattermost не настроен")
|
||||||
|
}
|
||||||
|
|
||||||
|
mdMessage := mmHTMLToMarkdown(message)
|
||||||
|
|
||||||
|
payload := map[string]string{
|
||||||
|
"text": mdMessage,
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonData, err := json.Marshal(payload)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("marshal error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[Mattermost] Отправка вебхук: %s", cfgMattermostWebhookURL)
|
||||||
|
|
||||||
|
client := &http.Client{Timeout: 15 * time.Second}
|
||||||
|
resp, err := client.Post(cfgMattermostWebhookURL, "application/json", bytes.NewBuffer(jsonData))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("ошибка отправки в Mattermost (webhook: %s): %w", cfgMattermostWebhookURL, err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
return fmt.Errorf("mattermost webhook error: status %d, body: %s", resp.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// mmHTMLToMarkdown конвертирует HTML-теги Telegram в Markdown для Mattermost.
|
||||||
|
//
|
||||||
|
// <b>текст</b> → **текст**
|
||||||
|
// <i>текст</i> → _текст_
|
||||||
|
// <code>текст</code> → `текст`
|
||||||
|
// <pre>текст</pre> → ```текст```
|
||||||
|
// ━━━ (разделители) → ---
|
||||||
|
// Остальные теги → удаляются
|
||||||
|
func mmHTMLToMarkdown(html string) string {
|
||||||
|
s := html
|
||||||
|
|
||||||
|
// <b>...</b> → **...**
|
||||||
|
s = regexp.MustCompile(`<b>([\s\S]*?)</b>`).ReplaceAllString(s, "**$1**")
|
||||||
|
|
||||||
|
// <i>...</i> → _..._
|
||||||
|
s = regexp.MustCompile(`<i>([\s\S]*?)</i>`).ReplaceAllString(s, "_$1_")
|
||||||
|
|
||||||
|
// <code>...</code> → `...`
|
||||||
|
s = regexp.MustCompile(`<code>([\s\S]*?)</code>`).ReplaceAllString(s, "`$1`")
|
||||||
|
|
||||||
|
// <pre>...</pre> → ```...```
|
||||||
|
s = regexp.MustCompile(`<pre>([\s\S]*?)</pre>`).ReplaceAllString(s, "```$1```")
|
||||||
|
|
||||||
|
// HTML-entities
|
||||||
|
s = strings.ReplaceAll(s, "<", "<")
|
||||||
|
s = strings.ReplaceAll(s, ">", ">")
|
||||||
|
s = strings.ReplaceAll(s, "&", "&")
|
||||||
|
s = strings.ReplaceAll(s, """, `"`)
|
||||||
|
|
||||||
|
// Горизонтальные разделители (━━━...) → ---
|
||||||
|
s = regexp.MustCompile(`━+`).ReplaceAllString(s, "---")
|
||||||
|
|
||||||
|
// Убираем оставшиеся HTML-теги
|
||||||
|
s = regexp.MustCompile(`<[^>]+>`).ReplaceAllString(s, "")
|
||||||
|
|
||||||
|
// Убираем лишние пустые строки (более двух подряд)
|
||||||
|
s = regexp.MustCompile(`\n{3,}`).ReplaceAllString(s, "\n\n")
|
||||||
|
|
||||||
|
return strings.TrimSpace(s)
|
||||||
|
}
|
||||||
58
telegram.go
58
telegram.go
|
|
@ -2,14 +2,58 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/proxy"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// telegramHTTPClient возвращает http.Client — обычный или через SOCKS5 прокси.
|
||||||
|
// Если TELEGRAM_SOCKS5_PROXY не задан — прямое соединение.
|
||||||
|
// Логин и пароль задаются отдельными параметрами TELEGRAM_SOCKS5_USER / TELEGRAM_SOCKS5_PASSWORD
|
||||||
|
// чтобы избежать проблем со спецсимволами при парсинге URL.
|
||||||
|
func telegramHTTPClient() (*http.Client, error) {
|
||||||
|
timeout := 15 * time.Second
|
||||||
|
|
||||||
|
if cfgTelegramSocks5Proxy == "" {
|
||||||
|
// Прямое соединение
|
||||||
|
return &http.Client{Timeout: timeout}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var auth *proxy.Auth
|
||||||
|
if cfgTelegramSocks5User != "" {
|
||||||
|
auth = &proxy.Auth{
|
||||||
|
User: cfgTelegramSocks5User,
|
||||||
|
Password: cfgTelegramSocks5Password,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dialer, err := proxy.SOCKS5("tcp", cfgTelegramSocks5Proxy, auth, proxy.Direct)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("ошибка создания SOCKS5 диалера: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
transport := &http.Transport{
|
||||||
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
|
return dialer.Dial(network, addr)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[Telegram] Используется SOCKS5 прокси: %s", cfgTelegramSocks5Proxy)
|
||||||
|
|
||||||
|
return &http.Client{
|
||||||
|
Transport: transport,
|
||||||
|
Timeout: timeout,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// telegramSendHTML отправляет HTML-сообщение в Telegram
|
// telegramSendHTML отправляет HTML-сообщение в Telegram
|
||||||
func telegramSendHTML(message string) error {
|
func telegramSendHTML(message string) error {
|
||||||
if !isTelegramConfigured() {
|
if !isTelegramConfigured() {
|
||||||
|
|
@ -17,6 +61,11 @@ func telegramSendHTML(message string) error {
|
||||||
return fmt.Errorf("telegram не настроен")
|
return fmt.Errorf("telegram не настроен")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
client, err := telegramHTTPClient()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("ошибка создания HTTP клиента: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
type tgMessage struct {
|
type tgMessage struct {
|
||||||
ChatID string `json:"chat_id"`
|
ChatID string `json:"chat_id"`
|
||||||
Text string `json:"text"`
|
Text string `json:"text"`
|
||||||
|
|
@ -36,7 +85,7 @@ func telegramSendHTML(message string) error {
|
||||||
|
|
||||||
apiURL := fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", cfgTelegramBotToken)
|
apiURL := fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", cfgTelegramBotToken)
|
||||||
|
|
||||||
resp, err := http.Post(apiURL, "application/json", bytes.NewBuffer(jsonData))
|
resp, err := client.Post(apiURL, "application/json", bytes.NewBuffer(jsonData))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("http error: %w", err)
|
return fmt.Errorf("http error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
@ -57,13 +106,18 @@ func telegramSendPlainText(message string) error {
|
||||||
return fmt.Errorf("telegram не настроен")
|
return fmt.Errorf("telegram не настроен")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
client, err := telegramHTTPClient()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("ошибка создания HTTP клиента: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
apiURL := fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", cfgTelegramBotToken)
|
apiURL := fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", cfgTelegramBotToken)
|
||||||
|
|
||||||
data := url.Values{}
|
data := url.Values{}
|
||||||
data.Set("chat_id", cfgTelegramChatID)
|
data.Set("chat_id", cfgTelegramChatID)
|
||||||
data.Set("text", message)
|
data.Set("text", message)
|
||||||
|
|
||||||
resp, err := http.PostForm(apiURL, data)
|
resp, err := client.PostForm(apiURL, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("http error: %w", err)
|
return fmt.Errorf("http error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue