Initial commit: AetherForge Linux (forge-mesh) v0.1.0-dev
Some checks failed
Test / test (push) Has been cancelled
Some checks failed
Test / test (push) Has been cancelled
This commit is contained in:
98
internal/alerts/telegram.go
Normal file
98
internal/alerts/telegram.go
Normal file
@@ -0,0 +1,98 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user