first commit

This commit is contained in:
Artem Tsyrulnikov
2026-02-05 15:41:56 +03:00
commit f833aeaf41
16 changed files with 1577 additions and 0 deletions

110
cmd/notifier.go Normal file
View File

@@ -0,0 +1,110 @@
package main
import (
"encoding/json"
"fmt"
"html"
"io"
"os"
"sync"
"github.com/NicoNex/echotron/v3"
)
// AdminNotifier is a writer that sends error logs to Telegram admins
type AdminNotifier struct {
api echotron.API
mu sync.Mutex
adminIDs map[int64]bool
writer io.Writer // Original writer to pass logs through
}
func NewAdminNotifier(api echotron.API, adminIDs map[int64]bool, writer io.Writer) *AdminNotifier {
return &AdminNotifier{
api: api,
adminIDs: adminIDs,
writer: writer,
}
}
func (n *AdminNotifier) Write(p []byte) (int, error) {
// Parse JSON log entry
var logEntry map[string]interface{}
if err := json.Unmarshal(p, &logEntry); err != nil {
// Not JSON, skip
return len(p), nil
}
// Check if this is an error log (only ERROR and FATAL, not WARN)
level, ok := logEntry["level"].(string)
if !ok || (level != "error" && level != "fatal") {
return len(p), nil
}
// Extract fields
message, _ := logEntry["message"].(string)
errorMsg, _ := logEntry["error"].(string)
timeStr, _ := logEntry["time"].(string)
// Build notification message
go n.sendNotification(message, errorMsg, timeStr, logEntry)
return len(p), nil
}
func (n *AdminNotifier) WriteLevel(level string, p []byte) (int, error) {
return n.Write(p)
}
func (n *AdminNotifier) sendNotification(message, errorMsg, timeStr string, logEntry map[string]interface{}) {
n.mu.Lock()
defer n.mu.Unlock()
// Build a compact formatted message
notificationMsg := "🚨 <b>Ошибка</b>\n"
if message != "" {
notificationMsg += html.EscapeString(message)
}
if errorMsg != "" {
notificationMsg += "\n" + html.EscapeString(errorMsg)
}
// Add only important contextual fields
var details []string
if groupID, ok := logEntry["group_id"]; ok {
details = append(details, fmt.Sprintf("группа: %v", groupID))
}
if userID, ok := logEntry["user_id"]; ok {
details = append(details, fmt.Sprintf("юзер: %v", userID))
}
if pollID, ok := logEntry["poll_id"]; ok {
details = append(details, fmt.Sprintf("опрос: %v", pollID))
}
if len(details) > 0 {
notificationMsg += "\n<i>" + html.EscapeString(fmt.Sprintf("(%s)", joinStrings(details, ", "))) + "</i>"
}
for adminID := range n.adminIDs {
opts := &echotron.MessageOptions{
ParseMode: echotron.HTML,
}
if _, err := n.api.SendMessage(notificationMsg, adminID, opts); err != nil {
// Fallback to stderr to avoid recursion with zerolog
fmt.Fprintf(os.Stderr, "Failed to send admin notification to %d: %v\n", adminID, err)
}
}
}
func joinStrings(strs []string, sep string) string {
if len(strs) == 0 {
return ""
}
result := strs[0]
for i := 1; i < len(strs); i++ {
result += sep + strs[i]
}
return result
}