159 lines
3.8 KiB
Go
159 lines
3.8 KiB
Go
|
|
package integrations
|
||
|
|
|
||
|
|
import (
|
||
|
|
"encoding/json"
|
||
|
|
"fmt"
|
||
|
|
"io"
|
||
|
|
"net/http"
|
||
|
|
"strconv"
|
||
|
|
"strings"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
type NtfyIntegration struct{}
|
||
|
|
|
||
|
|
var ntfyHTTPClient = &http.Client{
|
||
|
|
Timeout: 15 * time.Second,
|
||
|
|
}
|
||
|
|
|
||
|
|
type NtfyNotificationChannelConfig struct {
|
||
|
|
Endpoint string `json:"endpoint"`
|
||
|
|
AuthToken string `json:"authToken"`
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s NtfyIntegration) ProcessEvent(evt *ContactPointEvent, rawConfig string) error {
|
||
|
|
config := NtfyNotificationChannelConfig{}
|
||
|
|
err := json.Unmarshal([]byte(rawConfig), &config)
|
||
|
|
|
||
|
|
if err != nil {
|
||
|
|
return fmt.Errorf("failed to parse ntfy integration config - %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
if err := s.validate(config); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
switch evt.Type {
|
||
|
|
case ContactPointEventAlertTriggered:
|
||
|
|
data := evt.AlertTriggeredEvent
|
||
|
|
|
||
|
|
if data.SensorValueCondition != nil {
|
||
|
|
text := fmt.Sprintf("%s is at {value}", data.Sensor.Name)
|
||
|
|
|
||
|
|
if data.Alert.CustomMessage != "" {
|
||
|
|
text = data.Alert.CustomMessage
|
||
|
|
}
|
||
|
|
|
||
|
|
text = strings.Replace(text, "{value}", strconv.FormatFloat(data.LastValue, 'f', -1, 64), -1)
|
||
|
|
|
||
|
|
return s.send(config, "Alert Triggered", text)
|
||
|
|
}
|
||
|
|
|
||
|
|
if data.SensorLastContactCondition != nil {
|
||
|
|
text := fmt.Sprintf(
|
||
|
|
"%s has not reported in for last %s %s",
|
||
|
|
data.Sensor.Name,
|
||
|
|
strconv.FormatFloat(data.SensorLastContactCondition.Value, 'f', -1, 64),
|
||
|
|
data.SensorLastContactCondition.ValueUnit,
|
||
|
|
)
|
||
|
|
|
||
|
|
if data.Alert.CustomMessage != "" {
|
||
|
|
text = data.Alert.CustomMessage
|
||
|
|
}
|
||
|
|
|
||
|
|
return s.send(config, "Alert Triggered", text)
|
||
|
|
}
|
||
|
|
|
||
|
|
return nil
|
||
|
|
|
||
|
|
case ContactPointEventAlertResolved:
|
||
|
|
data := evt.AlertResolvedEvent
|
||
|
|
|
||
|
|
if data.SensorValueCondition != nil {
|
||
|
|
text := fmt.Sprintf("%s is at {value}", data.Sensor.Name)
|
||
|
|
|
||
|
|
if data.Alert.CustomResolvedMessage != "" {
|
||
|
|
text = data.Alert.CustomResolvedMessage
|
||
|
|
}
|
||
|
|
|
||
|
|
text = strings.Replace(text, "{value}", strconv.FormatFloat(data.LastValue, 'f', -1, 64), -1)
|
||
|
|
|
||
|
|
return s.send(config, "Alert Resolved", text)
|
||
|
|
}
|
||
|
|
|
||
|
|
if data.SensorLastContactCondition != nil {
|
||
|
|
text := fmt.Sprintf(
|
||
|
|
"%s has reported in last %s %s",
|
||
|
|
data.Sensor.Name,
|
||
|
|
strconv.FormatFloat(data.SensorLastContactCondition.Value, 'f', -1, 64),
|
||
|
|
data.SensorLastContactCondition.ValueUnit,
|
||
|
|
)
|
||
|
|
|
||
|
|
if data.Alert.CustomResolvedMessage != "" {
|
||
|
|
text = data.Alert.CustomResolvedMessage
|
||
|
|
}
|
||
|
|
|
||
|
|
return s.send(config, "Alert Resolved", text)
|
||
|
|
}
|
||
|
|
|
||
|
|
return nil
|
||
|
|
|
||
|
|
case ContactPointEventTest:
|
||
|
|
return s.send(config, "Test", "Test message from Basic Sensor Receiver")
|
||
|
|
}
|
||
|
|
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s NtfyIntegration) ValidateConfig(rawConfig string) error {
|
||
|
|
config := NtfyNotificationChannelConfig{}
|
||
|
|
err := json.Unmarshal([]byte(rawConfig), &config)
|
||
|
|
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
return s.validate(config)
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s NtfyIntegration) validate(config NtfyNotificationChannelConfig) error {
|
||
|
|
if strings.TrimSpace(config.Endpoint) == "" {
|
||
|
|
return fmt.Errorf("ntfy endpoint is required")
|
||
|
|
}
|
||
|
|
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s NtfyIntegration) send(config NtfyNotificationChannelConfig, title string, text string) error {
|
||
|
|
url := strings.TrimSpace(config.Endpoint)
|
||
|
|
|
||
|
|
req, err := http.NewRequest(http.MethodPost, url, strings.NewReader(text))
|
||
|
|
if err != nil {
|
||
|
|
return fmt.Errorf("failed to create ntfy request - %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
req.Header.Set("Content-Type", "text/plain; charset=utf-8")
|
||
|
|
req.Header.Set("Title", title)
|
||
|
|
|
||
|
|
if config.AuthToken != "" {
|
||
|
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", config.AuthToken))
|
||
|
|
}
|
||
|
|
|
||
|
|
resp, err := ntfyHTTPClient.Do(req)
|
||
|
|
if err != nil {
|
||
|
|
return fmt.Errorf("failed to send ntfy notification - %w", err)
|
||
|
|
}
|
||
|
|
defer resp.Body.Close()
|
||
|
|
|
||
|
|
if resp.StatusCode < 200 || resp.StatusCode > 299 {
|
||
|
|
body, readErr := io.ReadAll(resp.Body)
|
||
|
|
if readErr != nil {
|
||
|
|
return fmt.Errorf("ntfy returned status %d", resp.StatusCode)
|
||
|
|
}
|
||
|
|
|
||
|
|
return fmt.Errorf("ntfy returned status %d: %s", resp.StatusCode, strings.TrimSpace(string(body)))
|
||
|
|
}
|
||
|
|
|
||
|
|
return nil
|
||
|
|
}
|