99 lines
2.2 KiB
Go
99 lines
2.2 KiB
Go
package alerts
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// Config holds Telegram bot settings from config.json.
|
|
type Config struct {
|
|
Enabled bool `json:"enabled"`
|
|
BotToken string `json:"bot_token"`
|
|
ChatID string `json:"chat_id"`
|
|
}
|
|
|
|
// Notifier sends operator alerts via Telegram Bot API.
|
|
type Notifier struct {
|
|
cfg Config
|
|
client *http.Client
|
|
mu sync.Mutex
|
|
last time.Time
|
|
}
|
|
|
|
func New(cfg Config) *Notifier {
|
|
return &Notifier{
|
|
cfg: cfg,
|
|
client: &http.Client{
|
|
Timeout: 10 * time.Second,
|
|
},
|
|
}
|
|
}
|
|
|
|
// Enabled reports whether Telegram alerts are configured.
|
|
func (n *Notifier) Enabled() bool {
|
|
return n != nil && n.cfg.Enabled && n.cfg.BotToken != "" && n.cfg.ChatID != ""
|
|
}
|
|
|
|
// Send posts a message to the configured chat (best-effort, non-blocking caller).
|
|
func (n *Notifier) Send(text string) {
|
|
if !n.Enabled() {
|
|
return
|
|
}
|
|
|
|
go func() {
|
|
if err := n.sendSync(text); err != nil {
|
|
log.Printf("telegram alert: %v", err)
|
|
}
|
|
}()
|
|
}
|
|
|
|
func (n *Notifier) sendSync(text string) error {
|
|
n.mu.Lock()
|
|
if time.Since(n.last) < 500*time.Millisecond {
|
|
n.mu.Unlock()
|
|
time.Sleep(500 * time.Millisecond)
|
|
n.mu.Lock()
|
|
}
|
|
n.last = time.Now()
|
|
n.mu.Unlock()
|
|
|
|
url := fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", n.cfg.BotToken)
|
|
body, _ := json.Marshal(map[string]string{
|
|
"chat_id": n.cfg.ChatID,
|
|
"text": text,
|
|
"parse_mode": "Markdown",
|
|
})
|
|
|
|
req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(body))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
resp, err := n.client.Do(req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode >= 300 {
|
|
return fmt.Errorf("telegram API status %d", resp.StatusCode)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// FleetEvent formats a fleet alert message.
|
|
func FleetEvent(action, hostID, detail string) string {
|
|
return fmt.Sprintf("🔧 *forge-mesh* · `%s`\nhost: `%s`\n%s", action, hostID, detail)
|
|
}
|
|
|
|
// CrucibleEvent formats a batch terminal alert.
|
|
func CrucibleEvent(jobID string, hostCount int, cmd string) string {
|
|
return fmt.Sprintf("⚗️ *crucible batch* · `%s`\nhosts: %d\ncmd: `%s`", jobID, hostCount, cmd)
|
|
}
|