From 3d81d06ba9a61fa852f7b780c9aabb805d1c54f4 Mon Sep 17 00:00:00 2001 From: Matt Schmitz Date: Fri, 29 Mar 2024 14:55:12 -0400 Subject: [PATCH 1/2] Initial support for Pushover notifications --- README.md | 4 ++-- config/config.go | 38 ++++++++++++++++++++++++++++++++ docs/changelog.md | 4 ++++ docs/config.md | 52 ++++++++++++++++++++++++++++++++++++++++++++ docs/index.md | 1 + example-config.yml | 19 ++++++++++++++++ go.mod | 1 + go.sum | 2 ++ notifier/alerts.go | 3 +++ notifier/telegram.go | 2 +- 10 files changed, 123 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 036174a..9dc2559 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,11 @@ [![Static Badge](https://img.shields.io/badge/Project-Documentation-blue)](https://frigate-notify.0x2142.com) [![GitHub Repo stars](https://img.shields.io/github/stars/0x2142/frigate-notify)]() [![GitHub release (with filter)](https://img.shields.io/github/v/release/0x2142/frigate-notify)](https://github.com/0x2142/frigate-notify/releases) [![Static Badge](https://img.shields.io/badge/Docker-latest-blue)](https://github.com/0x2142/frigate-notify/pkgs/container/frigate-notify) - ## About -This project is designed to generate notifications based on [Frigate](https://github.com/blakeblackshear/frigate) NVR events. +This project is designed to generate event notifications from a standalone [Frigate](https://github.com/blakeblackshear/frigate) NVR instance. Currently Frigate only supports notifications through Home Assistant, which I'm not using right now. So I set out to build a simple notification app that would work with a standalone Frigate server. @@ -24,6 +23,7 @@ Currently Frigate only supports notifications through Home Assistant, which I'm - Gotify - SMTP - Telegram +- Pushover **Other** - Aliveness monitor via HTTP GET (for use with tools like [HealthChecks](https://github.com/healthchecks/healthchecks) or [Uptime Kuma](https://github.com/louislam/uptime-kuma)) diff --git a/config/config.go b/config/config.go index 7090780..8d2be4b 100644 --- a/config/config.go +++ b/config/config.go @@ -49,6 +49,7 @@ type Alerts struct { Gotify Gotify `fig:"gotify"` SMTP SMTP `fig:"smtp"` Telegram Telegram `fig:"telegram"` + Pushover Pushover `fig:"pushover"` } type General struct { @@ -89,6 +90,17 @@ type Telegram struct { Token string `fig:"token" default:""` } +type Pushover struct { + Enabled bool `fig:"enabled" default:false` + Token string `fig:"token" default:""` + Userkey string `fig:"userkey" default:""` + Devices string `fig:"devices" default:""` + Priority int `fig:"priority" default:0` + Retry int `fig:"retry" default:0` + Expire int `fig:"expire" default:0` + TTL int `fig:"ttl" default:0` +} + type Monitor struct { Enabled bool `fig:"enabled" default:false` URL string `fig:"url" default:""` @@ -229,6 +241,30 @@ func validateConfig() { configErrors = append(configErrors, "No Telegram bot token specified!") } } + if ConfigData.Alerts.Pushover.Enabled { + log.Print("Pushover alerting enabled.") + if ConfigData.Alerts.Pushover.Token == "" { + configErrors = append(configErrors, "No Pushover API token specified!") + } + if ConfigData.Alerts.Pushover.Userkey == "" { + configErrors = append(configErrors, "No Pushover user key specified!") + } + if ConfigData.Alerts.Pushover.Priority < -2 || ConfigData.Alerts.Pushover.Priority > 2 { + configErrors = append(configErrors, "Pushover priority must be between -2 and 2!") + } + // Priority 2 is emergency, needs a retry interval & expiration set + if ConfigData.Alerts.Pushover.Priority == 2 { + if ConfigData.Alerts.Pushover.Retry == 0 || ConfigData.Alerts.Pushover.Expire == 0 { + configErrors = append(configErrors, "Pushover retry interval & expiration must be set with priority 2!") + } + if ConfigData.Alerts.Pushover.Retry < 30 { + configErrors = append(configErrors, "Pushover retry cannot be less than 30 seconds!") + } + } + if ConfigData.Alerts.Pushover.TTL < 0 { + configErrors = append(configErrors, "Pushover TTL cannot be negative!") + } + } // Validate monitoring config if ConfigData.Monitor.Enabled { @@ -242,10 +278,12 @@ func validateConfig() { } if len(configErrors) > 0 { + fmt.Println() log.Println("Config validation failed:") for _, msg := range configErrors { log.Println(" -", msg) } + fmt.Println() log.Fatal("Please fix config errors before restarting app.") } else { log.Println("Config file validated!") diff --git a/docs/changelog.md b/docs/changelog.md index e77d306..2e3f4c6 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,5 +1,9 @@ # Changelog +## [v0.2.5](https://github.com/0x2142/frigate-notify/releases/tag/v0.2.5) - TBD + + - Added support for alerts via [Pushover](https://frigate-notify.0x2142.com/config/#pushover) + ## [v0.2.4](https://github.com/0x2142/frigate-notify/releases/tag/v0.2.4) - Mar 28 2024 - Added support for alerts via [Telegram](https://frigate-notify.0x2142.com/config/#telegram) diff --git a/docs/config.md b/docs/config.md index a85d996..ed10916 100644 --- a/docs/config.md +++ b/docs/config.md @@ -243,6 +243,44 @@ alerts: token: 987654321:ABCDEFGHIJKLMNOP ``` +### Pushover + +- **enabled** (Optional - Default: `false`) + - Set to `true` to enable alerting via Pushover +- **token** (Required) + - Pushover application API token + - Required if this alerting method is enabled +- **userkey** (Required) + - Recipient user or group key from Pushover dashboard + - Required if this alerting method is enabled +- **devices** (Optional) + - Optionally specify list of devices to send notifications to + - If left empty, all devices will receive the notification +- **priority** (Optional) + - Optionally set message priority + - Valid priorities are -2, -1, 0, 1, 2 +- **retry** (Optional) + - Message retry in seconds until message is acknowledged + - If `priority` is set to 2, this is required + - Minimum value is 30 seconds +- **expire** (Optional) + - Expiration timer for message retry + - If `priority` is set to 2, this is required +- **ttl** (Optional) + - Optionally set lifetime of message, in seconds + - If set, message notifications are deleted from devices after this time + +```yaml title="Config File Snippet" + pushover: + enabled: true + token: aaaaaaaaaaaaaaaaaaaaaa + userkey: bbbbbbbbbbbbbbbbbbbbbb + devices: device1,device2 + priority: 0 + retry: + expire: + ttl: +``` ## Monitor @@ -327,6 +365,20 @@ alerts: password: recipient: + telegram: + enabled: false + chatid: + token: + + pushover: + enabled: false + token: + userkey: + devices: + priority: + retry: + expire: + ttl: monitor: enabled: false diff --git a/docs/index.md b/docs/index.md index 9193b35..9fe025e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -23,6 +23,7 @@ Currently Frigate only supports notifications through Home Assistant, which I'm - Gotify - SMTP - Telegram +- Pushover **Other** diff --git a/example-config.yml b/example-config.yml index 30eca7d..3169257 100644 --- a/example-config.yml +++ b/example-config.yml @@ -97,6 +97,25 @@ alerts: # Bot API token token: + # Pushover Config + pushover: + # Set to true to enable alerting via Pushover + enabled: false + # Pushover API token for this application + token: + # User or Group key for recipients + userkey: + # Optional list of target devices by name, separated by comma + devices: + # Optional message priority, default is 0 + priority: + # If priority is 2, retry & expiration must be set + # Values in seconds. Retry must be 30 or higher + retry: + expire: + # Optional message lifetime + ttl: + ## App Monitoring # Sends HTTP GET to provided URL for aliveness checks diff --git a/go.mod b/go.mod index 5db1cd0..bda9931 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,7 @@ require ( github.com/disgoorg/json v1.1.0 // indirect github.com/disgoorg/snowflake/v2 v2.0.1 // indirect github.com/gorilla/websocket v1.5.1 // indirect + github.com/gregdel/pushover v1.3.0 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/pelletier/go-toml/v2 v2.1.1 // indirect diff --git a/go.sum b/go.sum index f6e3b76..a1dbb4c 100644 --- a/go.sum +++ b/go.sum @@ -16,6 +16,8 @@ github.com/gomarkdown/markdown v0.0.0-20231222211730-1d6d20845b47 h1:k4Tw0nt6lwr github.com/gomarkdown/markdown v0.0.0-20231222211730-1d6d20845b47/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA= github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= +github.com/gregdel/pushover v1.3.0 h1:CewbxqsThoN/1imgwkDKFkRkltaQMoyBV0K9IquQLtw= +github.com/gregdel/pushover v1.3.0/go.mod h1:EcaO66Nn1StkpEm1iKtBTV3d2A16SoMsVER1PthX7to= github.com/kkyr/fig v0.4.0 h1:4D/g72a8ij1fgRypuIbEoqIT7ukf2URVBtE777/gkbc= github.com/kkyr/fig v0.4.0/go.mod h1:U4Rq/5eUNJ8o5UvOEc9DiXtNf41srOLn2r/BfCyuc58= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= diff --git a/notifier/alerts.go b/notifier/alerts.go index f5b5378..2576c28 100644 --- a/notifier/alerts.go +++ b/notifier/alerts.go @@ -26,4 +26,7 @@ func SendAlert(message, snapshotURL string, snapshot io.Reader) { if config.ConfigData.Alerts.Telegram.Enabled { SendTelegramMessage(message, bytes.NewReader(snap)) } + if config.ConfigData.Alerts.Pushover.Enabled { + SendPushoverMessage(message, bytes.NewReader(snap)) + } } diff --git a/notifier/telegram.go b/notifier/telegram.go index 343976f..b5e932f 100644 --- a/notifier/telegram.go +++ b/notifier/telegram.go @@ -10,7 +10,7 @@ import ( "github.com/gomarkdown/markdown" ) -// SendTelegramMessage pushes alert message to Discord via webhook +// SendTelegramMessage sends alert through Telegram to individual users func SendTelegramMessage(message string, snapshot io.Reader) { bot, err := tgbotapi.NewBotAPI(config.ConfigData.Alerts.Telegram.Token) if err != nil { From df1330a19bc82131b4de20a0c79e3aa12443e63a Mon Sep 17 00:00:00 2001 From: Matt Schmitz Date: Fri, 29 Mar 2024 15:05:43 -0400 Subject: [PATCH 2/2] Add test mode for development --- config/config.go | 10 ++++++++ events/api.go | 12 ++++++--- notifier/pushover.go | 61 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 notifier/pushover.go diff --git a/config/config.go b/config/config.go index 8d2be4b..dacc524 100644 --- a/config/config.go +++ b/config/config.go @@ -27,6 +27,7 @@ type Frigate struct { type WebAPI struct { Enabled bool `fig:"enabled" default:false` Interval int `fig:"interval" default:30` + TestMode bool `fig:"testmode" default:false` } type MQTT struct { @@ -144,6 +145,15 @@ func validateConfig() { configErrors = append(configErrors, "Please configure only one polling method: Frigate Web API or MQTT") } + // Warn on test mode being enabled + if ConfigData.Frigate.WebAPI.Enabled && ConfigData.Frigate.WebAPI.TestMode { + log.Print("~~~~~~~~~~~~~~~~~~~") + log.Print("WARNING: Test Mode is enabled.") + log.Print("This is intended for development only & will only query Frigate for the last event.") + log.Print("Do not enable this in production! App will not accurately check for events.") + log.Print("~~~~~~~~~~~~~~~~~~~") + } + // Check if Frigate server URL contains protocol, assume HTTP if not specified if !strings.Contains(ConfigData.Frigate.Server, "http://") && !strings.Contains(ConfigData.Frigate.Server, "https://") { log.Println("No protocol specified on Frigate Server. Assuming http://. If this is incorrect, please adjust the config file.") diff --git a/events/api.go b/events/api.go index c763b03..589b2b1 100644 --- a/events/api.go +++ b/events/api.go @@ -22,9 +22,15 @@ var LastEventTime float64 = float64(time.Now().Unix()) // CheckForEvents queries for all detection events since last alert time func CheckForEvents() { - params := "?include_thumbnails=0&after=" + strconv.FormatFloat(LastEventTime, 'f', 6, 64) - // For testing, pull 1 event immediately - //params := "?include_thumbnails=0&limit=1" + var params string + if config.ConfigData.Frigate.WebAPI.TestMode { + // For testing, pull 1 event immediately + params = "?include_thumbnails=0&limit=1" + } else { + // Check for any events after last query time + params = "?include_thumbnails=0&after=" + strconv.FormatFloat(LastEventTime, 'f', 6, 64) + } + url := config.ConfigData.Frigate.Server + eventsURI + params log.Println("Checking for new events...") diff --git a/notifier/pushover.go b/notifier/pushover.go new file mode 100644 index 0000000..ac53b8a --- /dev/null +++ b/notifier/pushover.go @@ -0,0 +1,61 @@ +package notifier + +import ( + "fmt" + "io" + "log" + "strings" + "time" + + "github.com/0x2142/frigate-notify/config" + "github.com/gomarkdown/markdown" + "github.com/gregdel/pushover" +) + +// SendPushoverMessage sends alert message through Pushover service +func SendPushoverMessage(message string, snapshot io.Reader) { + push := pushover.New(config.ConfigData.Alerts.Pushover.Token) + recipient := pushover.NewRecipient(config.ConfigData.Alerts.Pushover.Userkey) + + // Convert message to HTML & strip newline characters + htmlMessage := string(markdown.ToHTML([]byte(message), nil, nil)) + htmlMessage = strings.Replace(htmlMessage, "\n", "", -1) + + // Create new message + notif := &pushover.Message{ + Message: htmlMessage, + Title: config.ConfigData.Alerts.General.Title, + Priority: config.ConfigData.Alerts.Pushover.Priority, + HTML: true, + TTL: time.Duration(config.ConfigData.Alerts.Pushover.TTL) * time.Second, + } + + // If emergency priority, set retry / expiration + if notif.Priority == 2 { + notif.Retry = time.Duration(config.ConfigData.Alerts.Pushover.Retry) * time.Second + notif.Expire = time.Duration(config.ConfigData.Alerts.Pushover.Expire) * time.Second + fmt.Print(notif.Retry, notif.Expire) + } + + // Add target devices if specified + if config.ConfigData.Alerts.Pushover.Devices != "" { + devices := strings.ReplaceAll(config.ConfigData.Alerts.Pushover.Devices, " ", "") + notif.DeviceName = devices + } + + // Send notification + if snapshot != nil { + notif.AddAttachment(snapshot) + if _, err := push.SendMessage(notif, recipient); err != nil { + log.Print("Error sending Pushover notification:", err) + return + } + } else { + if _, err := push.SendMessage(notif, recipient); err != nil { + log.Print("Error sending Pushover notification:", err) + return + } + } + + log.Println("Pushover alert sent") +}