diff --git a/Readme.md b/Readme.md index 732aa10..08be33d 100644 --- a/Readme.md +++ b/Readme.md @@ -101,6 +101,8 @@ _A_: Some IP cameras have ONVIF, and sometimes that even includes motion alarms, - Misecu 5MP Wifi AI Cam (Hisilicon server) +- Lorex N8428-Z 4K NVR (Dahua server) + - HomeViz OB10, K4W10, OW10 (Hisilicon server, tested by [acburnett](https://github.com/acburnett)) If your camera works with Alarm Server - create an issue with some details about it and a picture, and we'll post it here. diff --git a/buses/webhooks/webhooks.go b/buses/webhooks/webhooks.go index 8d34037..1687565 100644 --- a/buses/webhooks/webhooks.go +++ b/buses/webhooks/webhooks.go @@ -5,9 +5,10 @@ import ( "encoding/json" "fmt" "github.com/toxuin/alarmserver/config" - "io/ioutil" + "io" "net/http" "strings" + "text/template" ) type Bus struct { @@ -17,8 +18,9 @@ type Bus struct { } type WebhookPayload struct { - Topic string `json:"topic"` - Data string `json:"data"` + CameraName string `json:"cameraName"` + EventType string `json:"eventType"` + Extra string `json:"extra"` } func (webhooks *Bus) Initialize(conf config.WebhooksConfig) { @@ -42,9 +44,9 @@ func (webhooks *Bus) Initialize(conf config.WebhooksConfig) { } } -func (webhooks *Bus) SendMessage(topic string, data string) { +func (webhooks *Bus) SendMessage(cameraName string, eventType string, extra string) { for _, webhook := range webhooks.webhooks { - payload := WebhookPayload{Topic: topic, Data: data} + payload := WebhookPayload{CameraName: cameraName, EventType: eventType, Extra: extra} go webhooks.send(webhook, payload) } } @@ -56,7 +58,57 @@ func (webhooks *Bus) send(webhook config.WebhookConfig, payload WebhookPayload) return } - request, err := http.NewRequest(webhook.Method, webhook.Url, bytes.NewBuffer(payloadJson)) + var templateVars = map[string]interface{}{ + "Camera": payload.CameraName, + "Event": payload.EventType, + "Extra": payload.Extra, + } + + // PARSE WEBHOOK URL AS TEMPLATE + urlTemplate, err := template.New("webhookUrl").Parse(webhook.Url) + if err != nil { + fmt.Printf("WEBHOOKS: Error parsing webhook URL as template: %s\n", webhook.Url) + if webhooks.Debug { + fmt.Println("Webhooks: Error", err) + } + return + } + var urlBuffer bytes.Buffer + err = urlTemplate.Execute(&urlBuffer, templateVars) + if err != nil { + fmt.Printf("WEBHOOKS: Error rendering webhook URL as template: %s\n", webhook.Url) + if webhooks.Debug { + fmt.Println("Webhooks: Error", err) + } + return + } + url := urlBuffer.String() + + // PARSE BODY AS TEMPLATE + body := bytes.NewBuffer(payloadJson) + if webhook.BodyTemplate != "" { + bodyTemplate, err := template.New("payload").Parse(webhook.BodyTemplate) + if err != nil { + fmt.Printf("WEBHOOKS: Error parsing webhook body as template: %s\n", webhook.Url) + if webhooks.Debug { + fmt.Println("Webhooks: Error", err) + } + return + } + + var bodyBuffer bytes.Buffer + err = bodyTemplate.Execute(&bodyBuffer, templateVars) + if err != nil { + fmt.Printf("WEBHOOKS: Error rendering webhook body as template: %s\n", webhook.Url) + if webhooks.Debug { + fmt.Println("Webhooks: Error", err) + } + return + } + body = &bodyBuffer + } + + request, err := http.NewRequest(webhook.Method, url, body) if err != nil { fmt.Printf("WEBHOOKS: Error creating %s request to %s\n", webhook.Method, webhook.Url) if webhooks.Debug { @@ -93,7 +145,7 @@ func (webhooks *Bus) send(webhook config.WebhookConfig, payload WebhookPayload) ) } if webhooks.Debug { - bodyBytes, _ := ioutil.ReadAll(response.Body) + bodyBytes, _ := io.ReadAll(response.Body) bodyStr := string(bodyBytes) if len(bodyStr) == 0 { bodyStr = "*empty*" diff --git a/config/config.go b/config/config.go index 0027277..399f922 100644 --- a/config/config.go +++ b/config/config.go @@ -34,9 +34,10 @@ type WebhooksConfig struct { } type WebhookConfig struct { - Url string `json:"url"` - Method string `json:"method"` - Headers []string `json:"headers"` + Url string `json:"url"` + Method string `json:"method"` + Headers []string `json:"headers"` + BodyTemplate string `json:"bodyTemplate"` } type HisiliconConfig struct { diff --git a/docs/config.yaml b/docs/config.yaml index ab6019c..b02aeff 100644 --- a/docs/config.yaml +++ b/docs/config.yaml @@ -56,6 +56,13 @@ webhooks: method: "GET" # DEFAULTS TO POST headers: - "X-Beep: boop" + + # YOU CAN USE TEMPLATE VARIABLES TO FORM THE URL: .Camera, .Event, .Extra + - url: "https://example.com/webhooks/{{ .Camera }}/events/{{ .Event }}" + # YOU CAN ALSO USE TEMPLATE VARIABLES IN THE PAYLOAD BODY! + # BELOW EXAMPLE DELIVERS RAW EVENT TO THE ENDPOINT + bodyTemplate: '{{ .Extra }}' + - url: "https://api.telegram.org/bot121212121:token/sendMessage?chat_id=43434343434&text=hello" # SIMPLE SHORTHAND FORM FOR THE SAME STUFF AS ABOVE, WILL PERFORM A POST TO EACH URL diff --git a/main.go b/main.go index 7682998..0e373e3 100644 --- a/main.go +++ b/main.go @@ -47,12 +47,12 @@ func main() { } } - messageHandler := func(topic string, data string) { + messageHandler := func(cameraName string, eventType string, extra string) { if config.Mqtt.Enabled { - mqttBus.SendMessage(config.Mqtt.TopicRoot+"/"+topic, data) + mqttBus.SendMessage(config.Mqtt.TopicRoot+"/"+cameraName+"/"+eventType, extra) } if config.Webhooks.Enabled { - webhookBus.SendMessage(topic, data) + webhookBus.SendMessage(cameraName, eventType, extra) } } diff --git a/servers/dahua/server.go b/servers/dahua/server.go index 933ae85..1f3b527 100644 --- a/servers/dahua/server.go +++ b/servers/dahua/server.go @@ -28,7 +28,7 @@ type Server struct { Debug bool WaitGroup *sync.WaitGroup Cameras *[]DhCamera - MessageHandler func(topic string, data string) + MessageHandler func(cameraName string, eventType string, extra string) } type DhEvent struct { @@ -254,8 +254,8 @@ func (server *Server) Start() { if server.MessageHandler == nil { fmt.Println("DAHUA: Message handler is not set for Dahua cams - that's probably not what you want") - server.MessageHandler = func(topic string, data string) { - fmt.Printf("DAHUA: Lost alarm: %s: %s\n", topic, data) + server.MessageHandler = func(cameraName string, eventType string, extra string) { + fmt.Printf("DAHUA: Lost alarm: %s - %s: %s\n", cameraName, eventType, extra) } } @@ -277,7 +277,7 @@ func (server *Server) Start() { for { event := <-channel - go server.MessageHandler(event.Camera.Name+"/"+event.Type, event.Message) + go server.MessageHandler(event.Camera.Name, event.Type, event.Message) } }(&waitGroup, eventChannel) diff --git a/servers/ftp/server.go b/servers/ftp/server.go index 1343b60..314d352 100644 --- a/servers/ftp/server.go +++ b/servers/ftp/server.go @@ -13,7 +13,7 @@ type Server struct { AllowFiles bool RootPath string Password string - MessageHandler func(topic string, data string) + MessageHandler func(cameraName string, eventType string, extra string) } type Event struct { @@ -25,8 +25,8 @@ type Event struct { func (serv *Server) Start() { if serv.MessageHandler == nil { fmt.Println("FTP: Message handler is not set for FTP server - that's probably not what you want") - serv.MessageHandler = func(topic string, data string) { - fmt.Printf("FTP: Lost alarm: %s: %s\n", topic, data) + serv.MessageHandler = func(cameraName string, eventType string, extra string) { + fmt.Printf("FTP: Lost alarm: %s - %s: %s\n", cameraName, eventType, extra) } } // DEFAULT FTP PASSWORD @@ -44,7 +44,7 @@ func (serv *Server) Start() { go func(channel <-chan Event) { for { event := <-channel - go serv.MessageHandler(event.CameraName+"/"+event.Type, event.Message) + go serv.MessageHandler(event.CameraName, event.Type, event.Message) } }(eventChannel) diff --git a/servers/hikvision/server.go b/servers/hikvision/server.go index 78412b1..dd0ff48 100644 --- a/servers/hikvision/server.go +++ b/servers/hikvision/server.go @@ -37,7 +37,7 @@ type Server struct { Debug bool WaitGroup *sync.WaitGroup Cameras *[]HikCamera - MessageHandler func(topic string, data string) + MessageHandler func(cameraName string, eventType string, extra string) } type XmlEvent struct { @@ -153,8 +153,8 @@ func (server *Server) Start() { if server.MessageHandler == nil { fmt.Println("HIK: Message handler is not set for Hikvision cams - that's probably not what you want") - server.MessageHandler = func(topic string, data string) { - fmt.Printf("HIK: Lost alarm: %s: %s\n", topic, data) + server.MessageHandler = func(cameraName string, eventType string, extra string) { + fmt.Printf("HIK: Lost alarm: %s - %s: %s\n", cameraName, eventType, extra) } } @@ -176,7 +176,7 @@ func (server *Server) Start() { server.WaitGroup.Add(1) for { event := <-channel - go server.MessageHandler(event.Camera.Name+"/"+event.Type, event.Message) + go server.MessageHandler(event.Camera.Name, event.Type, event.Message) } }(&cameraWaitGroup, eventChannel) diff --git a/servers/hisilicon/server.go b/servers/hisilicon/server.go index 0afc8cc..39ee4a3 100644 --- a/servers/hisilicon/server.go +++ b/servers/hisilicon/server.go @@ -41,7 +41,7 @@ type Server struct { Debug bool WaitGroup *sync.WaitGroup Port string - MessageHandler func(topic string, data string) + MessageHandler func(cameraName string, eventType string, extra string) } func (server *Server) handleTcpConnection(conn net.Conn) { @@ -93,7 +93,7 @@ func (server *Server) handleTcpConnection(conn net.Conn) { serialId := fmt.Sprintf("%v", dataMap["SerialID"]) event := fmt.Sprintf("%v", dataMap["Event"]) - server.MessageHandler(serialId+"/"+event, string(jsonBytes)) + server.MessageHandler(serialId, event, string(jsonBytes)) } func (server *Server) Start() { @@ -102,8 +102,8 @@ func (server *Server) Start() { } if server.MessageHandler == nil { fmt.Println("HISI: Message handler is not set for HiSilicon cams - that's probably not what you want") - server.MessageHandler = func(topic string, data string) { - fmt.Printf("HISI: Lost alarm: %s: %s\n", topic, data) + server.MessageHandler = func(cameraName string, eventType string, extra string) { + fmt.Printf("HISI: Lost alarm: %s - %s: %s\n", cameraName, eventType, extra) } }