Skip to content

Commit

Permalink
Merge pull request #204 from 0x2142/support-for-signal
Browse files Browse the repository at this point in the history
Add support for Signal
  • Loading branch information
0x2142 authored Feb 5, 2025
2 parents 28bc28c + c12e064 commit 6db8406
Show file tree
Hide file tree
Showing 12 changed files with 232 additions and 16 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Frigate-Notify is a simple app designed to send notifications from [Frigate](htt
- Mattermost
- Ntfy
- Pushover
- Signal
- SMTP
- Telegram
- Webhook
Expand Down
42 changes: 42 additions & 0 deletions config/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,17 @@ func (c *Config) Validate() []string {
}
}

// Validate Signal
Internal.Status.Notifications.Signal = make([]models.NotifierStatus, len(c.Alerts.Signal))
for id, profile := range c.Alerts.Signal {
Internal.Status.Notifications.Signal[id].InitNotifStatus(id, profile.Enabled)
if profile.Enabled {
if results := c.validateSignal(id); len(results) > 0 {
validationErrors = append(validationErrors, results...)
}
}
}

// Validate SMTP
Internal.Status.Notifications.SMTP = make([]models.NotifierStatus, len(c.Alerts.SMTP))
for id, profile := range c.Alerts.SMTP {
Expand All @@ -135,6 +146,7 @@ func (c *Config) Validate() []string {
}
}
}

// Validate Telegram
Internal.Status.Notifications.Telegram = make([]models.NotifierStatus, len(c.Alerts.Telegram))
for id, profile := range c.Alerts.Telegram {
Expand Down Expand Up @@ -566,6 +578,31 @@ func (c *Config) validatePushover(id int) []string {
return pushoverErrors
}

func (c *Config) validateSignal(id int) []string {
var signalErrors []string
log.Debug().Msgf("Alerting enabled for Signal profile ID %v", id)
if c.Alerts.Signal[id].Server == "" {
signalErrors = append(signalErrors, fmt.Sprintf("No Signal server specified! Profile ID %v", id))
}
if c.Alerts.Signal[id].Account == "" {
signalErrors = append(signalErrors, fmt.Sprintf("No Signal account specified! Profile ID %v", id))
}
// Check if Signal server URL contains protocol, assume HTTP if not specified
if !strings.Contains(c.Alerts.Signal[id].Server, "http://") && !strings.Contains(c.Alerts.Signal[id].Server, "https://") {
log.Debug().Msgf("No protocol specified on Signal Server. Assuming http://. If this is incorrect, please adjust the config file. Profile ID %v", id)
c.Alerts.Signal[id].Server = fmt.Sprintf("http://%s", c.Alerts.Signal[id].Server)
}
// Check recipients list
if len(c.Alerts.Signal[id].Recipients) == 0 {
log.Debug().Msgf("No message recipients configured for Signal. Profile ID %v", id)
}
// Check template syntax
if msg := validateTemplate("Signal", c.Alerts.Signal[id].Template); msg != "" {
signalErrors = append(signalErrors, msg+fmt.Sprintf(" Profile ID %v", id))
}
return signalErrors
}

func (c *Config) validateSMTP(id int) []string {
var smtpErrors []string
log.Debug().Msgf("Alerting enabled for SMTP profile ID %v", id)
Expand Down Expand Up @@ -650,6 +687,11 @@ func (c *Config) validateAlertingEnabled() string {
return ""
}
}
for _, profile := range c.Alerts.Signal {
if profile.Enabled {
return ""
}
}
for _, profile := range c.Alerts.SMTP {
if profile.Enabled {
return ""
Expand Down
1 change: 1 addition & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Changelog

## [v0.4.1](https://github.com/0x2142/frigate-notify/releases/tag/v0.4.1) - In Development
- Add support for notifications via [Signal](https://frigate-notify.0x2142.com/latest/config/file/#signal)
- Add support for notifications via [Mattermost](https://frigate-notify.0x2142.com/latest/config/file/#mattermost)
- Fix issue with alert-level filters where notification would not be sent if first detected label was filtered

Expand Down
63 changes: 51 additions & 12 deletions docs/config/file.md
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,18 @@ alerts:
- Optionally specify a custom notification template
- For more information on template syntax, see [Alert Templates](./templates.md#alert-templates)

```yaml title="Config File Snippet"
mattermost:
enabled: false
webhook: https://mattermost.your.domain.tld
channel: frigate-notifications
username: frigate-notify
priority: standard
ignoressl: true
headers:
template:
```

### Ntfy

- **enabled** (Optional - Default: `false`)
Expand Down Expand Up @@ -452,6 +464,45 @@ alerts:
template:
```

### Signal

!!!important
Signal notifications rely on an external service to handle communication to Signal: https://github.com/bbernhard/signal-cli-rest-api

Please follow the instructions on the [signal-cli-rest-api](https://github.com/bbernhard/signal-cli-rest-api) repo for set up & configuration. This service exposes a REST API that Frigate-Notify uses to forward notifications to Signal.

- **enabled** (Optional - Default: `false`)
- Set to `true` to enable alerting via Ntfy
- **server** (Required)
- Full URL of the desired [signal-cli-rest-api](https://github.com/bbernhard/signal-cli-rest-api) container
- Required if this alerting method is enabled
- **account** (Required)
- Signal account used to send notifications
- This is the full phone number including country code (ex. `+12223334444`)
- Required if this alerting method is enabled
- **recipients** (Required)
- One or more Signal recipients that will receive notifications
- This is the full phone number including country code (ex. `+12223334444`)
- Required if this alerting method is enabled
- **ignoressl** (Optional - Default: `false`)
- Set to `true` to allow self-signed certificates
- **template** (Optional)
- Optionally specify a custom notification template
- For more information on template syntax, see [Alert Templates](./templates.md#alert-templates)

```yaml title="Config File Snippet"
alerts:
signal:
enabled: false
server: https://signal-cli-rest-api.your.domain.tld
account: +12223334444
recipients:
- +15556667777
ignoressl: true
template:
```


### SMTP

- **enabled** (Optional - Default: `false`)
Expand Down Expand Up @@ -611,18 +662,6 @@ alerts:
template:
```

```yaml title="Config File Snippet"
mattermost:
enabled: false
webhook:
channel:
username:
priority:
ignoressl:
headers:
template:
```

## Monitor

If enabled, this application will check in with tools like [HealthChecks](https://github.com/healthchecks/healthchecks) or [Uptime Kuma](https://github.com/louislam/uptime-kuma) on a regular interval for health / status monitoring.
Expand Down
8 changes: 8 additions & 0 deletions docs/config/sample.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,14 @@ alerts:
ttl:
template:

signal:
enabled: false
server:
account:
recipients:
ignoressl:
template:

smtp:
enabled: false
server:
Expand Down
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Frigate-Notify is a simple app designed to send notifications from [Frigate](htt
- Mattermost
- Ntfy
- Pushover
- Signal
- SMTP
- Telegram
- Webhook
Expand Down
17 changes: 17 additions & 0 deletions example-config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,23 @@ alerts:
# Custom notification template, if desired
template:

# Signal Config
signal:
# Set to true to enable alerting via Signal
enabled: false
# IP or hostname of signal-cli-rest-api container (See docs for more info)
server:
# Signal number to use for sending messages (ex. +12223334444)
account:
# List of recipients to send messages to
recipients:
# Example:
# - +15556667777
# Set to true to allow self-signed certificates
ignoressl:
# Custom notification template, if desired
template:

smtp:
# Set to true to enable alerting via SMTP
enabled: false
Expand Down
17 changes: 14 additions & 3 deletions models/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,13 @@ type Alerts struct {
SubLabels Labels `fig:"sublabels" json:"sublabels,omitempty" doc:"Allow/Block sublabels from alerting"`
Discord []Discord `fig:"discord" json:"discord,omitempty" doc:"Discord notification settings"`
Gotify []Gotify `fig:"gotify" json:"gotify,omitempty" doc:"Gotify notification settings"`
Mattermost []Mattermost `fig:"mattermost" json:"mattermost,omitempty" doc:"Mattermost notification settings"`
Ntfy []Ntfy `fig:"ntfy" json:"ntfy,omitempty" doc:"Ntfy notification settings"`
Pushover []Pushover `fig:"pushover" json:"pushover,omitempty" doc:"Pushover notification settings"`
Signal []Signal `fig:"signal" json:"signal,omitempty" doc:"Signal notification settings`
SMTP []SMTP `fig:"smtp" json:"smtp,omitempty" doc:"SMTP notification settings"`
Telegram []Telegram `fig:"telegram" json:"telegram,omitempty" doc:"Telegram notification settings"`
Pushover []Pushover `fig:"pushover" json:"pushover,omitempty" doc:"Pushover notification settings"`
Ntfy []Ntfy `fig:"ntfy" json:"ntfy,omitempty" doc:"Ntfy notification settings"`
Webhook []Webhook `fig:"webhook" json:"webhook,omitempty" doc:"Webhook notification settings"`
Mattermost []Mattermost `fig:"mattermost" json:"mattermost,omitempty" doc:"Mattermost notification settings"`
}

type General struct {
Expand Down Expand Up @@ -152,6 +153,16 @@ type Pushover struct {
Filters AlertFilter `fig:"filters" json:"filters,omitempty" doc:"Filter notifications sent via this provider"`
}

type Signal struct {
Enabled bool `fig:"enabled" json:"enabled" enum:"true,false" doc:"Enable notifications via Signal" default:false`
Server string `fig:"server" json:"server,omitempty" doc:"Signal REST API server hostname or IP address" default:""`
Account string `fig:"account" json:"account,omitempty" doc:"Number of account used to send messages" default:""`
Recipients []string `fig:"recipients" json:"recipients,omitempty" doc:"List of recipients to receive messages" default:[]`
Insecure bool `fig:"ignoressl" enum:"true,false" json:"ignoressl,omitempty" default:false`
Template string `fig:"template" json:"template,omitempty" doc:"Custom message template" default:""`
Filters AlertFilter `fig:"filters" json:"filters,omitempty" doc:"Filter notifications sent via this provider"`
}

type SMTP struct {
Enabled bool `fig:"enabled" json:"enabled" enum:"true,false" doc:"Enable notifications via SMTP" default:false`
Server string `fig:"server" json:"server,omitempty" doc:"SMTP server hostname or IP address" default:""`
Expand Down
1 change: 1 addition & 0 deletions models/internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type Notifiers struct {
Gotify []NotifierStatus `json:"gotify" doc:"Status of Gotify notifications"`
Ntfy []NotifierStatus `json:"ntfy" doc:"Status of Ntfy notifications"`
Pushover []NotifierStatus `json:"pushover" doc:"Status of Pushover notifications"`
Signal []NotifierStatus `json:"signal" doc:"Status of Signal notifications"`
SMTP []NotifierStatus `json:"smtp" doc:"Status of SMTP notifications"`
Telegram []NotifierStatus `json:"telegram" doc:"Status of Telegram notifications"`
Webhook []NotifierStatus `json:"webhook" doc:"Status of Webhook notifications"`
Expand Down
9 changes: 9 additions & 0 deletions notifier/alerts.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,15 @@ func SendAlert(events []models.Event) {
}
}
}
// Signal
for id, profile := range config.ConfigData.Alerts.Signal {
if profile.Enabled {
provider := notifMeta{name: "signal", index: id}
if checkAlertFilters(events, profile.Filters, provider) {
go SendSignalMessage(event, bytes.NewReader(snap), provider)
}
}
}
// SMTP
for id, profile := range config.ConfigData.Alerts.SMTP {
if profile.Enabled {
Expand Down
2 changes: 1 addition & 1 deletion notifier/filters.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ func checkAlertFilters(events []models.Event, filters models.AlertFilter, provid
if len(filters.Labels) >= 1 {
match := false
for _, label := range labels {
if !slices.Contains(filters.Labels, label) {
if slices.Contains(filters.Labels, label) {
match = true
break
}
Expand Down
86 changes: 86 additions & 0 deletions notifier/signal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package notifier

import (
"encoding/base64"
"encoding/json"
"io"
"strings"

"github.com/rs/zerolog/log"

"github.com/0x2142/frigate-notify/config"
"github.com/0x2142/frigate-notify/models"
"github.com/0x2142/frigate-notify/util"
)

type SignalPayload struct {
Number string `json:"number"`
Recipients []string `json:"recipients,omitempty"`
Message string `json:"message"`
Attachments []string `json:"base64_attachments"`
}

// SendSignalMessage pushes alert message to Signal via webhook
func SendSignalMessage(event models.Event, snapshot io.Reader, provider notifMeta) {
profile := config.ConfigData.Alerts.Signal[provider.index]
status := &config.Internal.Status.Notifications.Signal[provider.index]

var err error
var message string
// Build notification
if profile.Template != "" {
message = renderMessage(profile.Template, event, "message", "Signal")
} else {
message = renderMessage("plaintext", event, "message", "Signal")
}

if !strings.HasPrefix(profile.Account, "+") {
profile.Account = "+" + profile.Account
}
var recipients []string
for _, recipient := range profile.Recipients {
if !strings.HasPrefix(recipient, "+") {
recipients = append(recipients, "+"+recipient)
} else {
recipients = append(recipients, recipient)
}
}

// Build payload
payload := SignalPayload{Message: message, Number: profile.Account, Recipients: recipients}
img, _ := io.ReadAll(snapshot)
attach := base64.StdEncoding.EncodeToString(img)
payload.Attachments = append(payload.Attachments, attach)

data, err := json.Marshal(payload)
if err != nil {
log.Warn().
Str("event_id", event.ID).
Str("provider", "Signal").
Err(err).
Int("provider_id", provider.index).
Msg("Unable to send alert")
status.NotifFailure(err.Error())
return
}

url := profile.Server + "/v2/send"
_, err = util.HTTPPost(url, profile.Insecure, []byte(data), "")

if err != nil {
log.Warn().
Str("event_id", event.ID).
Str("provider", "Signal").
Int("provider_id", provider.index).
Err(err).
Msg("Unable to send alert")
status.NotifFailure(err.Error())
return
}
log.Info().
Str("event_id", event.ID).
Str("provider", "Signal").
Int("provider_id", provider.index).
Msg("Alert sent")
status.NotifSuccess()
}

0 comments on commit 6db8406

Please sign in to comment.