diff --git a/docs/config/index.md b/docs/config/index.md index 26342998..4cd65c7a 100644 --- a/docs/config/index.md +++ b/docs/config/index.md @@ -56,6 +56,11 @@ You can override this using the [`--config` flag or `CONFIG` env var with `serve to: - webmaster@example.com - me@example.com + ntfy: + endpoint: https://ntfy.sh + topic: diun-acce65a0-b777-46f9-9a11-58c67d1579c4 + priority: 3 + timeout: 5s rocketchat: endpoint: http://rocket.foo.com:3000 channel: "#general" @@ -131,6 +136,11 @@ All configuration from file can be transposed into environment variables. As an token: Token123456 priority: 1 timeout: 10s + ntfy: + endpoint: https://ntfy.sh + topic: diun-acce65a0-b777-46f9-9a11-58c67d1579c4 + priority: 3 + timeout: 5s telegram: token: aabbccdd:11223344 chatIDs: @@ -179,6 +189,11 @@ Can be transposed to: DIUN_NOTIF_GOTIFY_PRIORITY=1 DIUN_NOTIF_GOTIFY_TIMEOUT=10s + DIUN_NOTIF_NTFY_ENDPOINT=https://ntfy.sh + DIUN_NOTIF_NTFY_TOPIC=diun-acce65a0-b777-46f9-9a11-58c67d1579c4 + DIUN_NOTIF_NTFY_TAGS=whale + DIUN_NOTIF_NTFY_TIMEOUT=10s + DIUN_NOTIF_TELEGRAM_TOKEN=aabbccdd:11223344 DIUN_NOTIF_TELEGRAM_CHATIDS=123456789,987654321 @@ -213,6 +228,7 @@ Can be transposed to: * [mail](../notif/mail.md) * [matrix](../notif/matrix.md) * [mqtt](../notif/mqtt.md) + * [ntfy](../notif/ntfy.md) * [pushover](../notif/pushover.md) * [rocketchat](../notif/rocketchat.md) * [script](../notif/script.md) diff --git a/docs/config/notif.md b/docs/config/notif.md index 939c9cf3..7705b155 100644 --- a/docs/config/notif.md +++ b/docs/config/notif.md @@ -6,6 +6,7 @@ * [`mail`](../notif/mail.md) * [`matrix`](../notif/matrix.md) * [`mqtt`](../notif/mqtt.md) +* [`ntfy`](../notif/ntfy.md) * [`pushover`](../notif/pushover.md) * [`rocketchat`](../notif/rocketchat.md) * [`script`](../notif/script.md) diff --git a/docs/notif/ntfy.md b/docs/notif/ntfy.md new file mode 100644 index 00000000..55a61e2d --- /dev/null +++ b/docs/notif/ntfy.md @@ -0,0 +1,53 @@ +# Ntfy notifications + +Notifications can be sent using a [ntfy](https://ntfy.sh/) instance. + +## Configuration + +!!! example "File" + ```yaml + notif: + ntfy: + endpoint: https://ntfy.sh + topic: diun-acce65a0-b777-46f9-9a11-58c67d1579c4 + priority: 3 + tags: + - whale + timeout: 10s + templateTitle: "{{ .Entry.Image }} released" + templateBody: | + Docker tag {{ .Entry.Image }} which you subscribed to through {{ .Entry.Provider }} provider has been released. + ``` + +| Name | Default | Description | +| ------------------- | ----------------------------------- | -------------------------------------------------------------------------- | +| `endpoint`[^1] | `https://ntfy.sh` | Ntfy base URL | +| `topic` | | Ntfy topic | +| `priority` | 3 | The priority of the message | +| `tags` | `["package"]` | Emoji to go in your notiication | +| `timeout` | `10s` | Timeout specifies a time limit for the request to be made | +| `templateTitle`[^1] | See [below](#default-templatetitle) | [Notification template](../faq.md#notification-template) for message title | +| `templateBody`[^1] | See [below](#default-templatebody) | [Notification template](../faq.md#notification-template) for message body | + +!!! abstract "Environment variables" + * `DIUN_NOTIF_NTFY_ENDPOINT` + * `DIUN_NOTIF_NTFY_TOPIC` + * `DIUN_NOTIF_NTFY_PRIORITY` + * `DIUN_NOTIF_NTFY_TAGS` + * `DIUN_NOTIF_NTFY_TIMEOUT` + * `DIUN_NOTIF_NTFY_TEMPLATETITLE` + * `DIUN_NOTIF_NTFY_TEMPLATEBODY` + +### Default `templateTitle` + +``` +[[ config.extra.template.notif.defaultTitle ]] +``` + +### Default `templateBody` + +``` +[[ config.extra.template.notif.defaultBody ]] +``` + +[^1]: Value required diff --git a/internal/config/config_test.go b/internal/config/config_test.go index b4f7de77..1788a08e 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -127,6 +127,15 @@ for {{ .Entry.Manifest.Platform }} platform. Topic: "docker/diun", QoS: 0, }, + Ntfy: &model.NotifNtfy{ + Endpoint: "https://ntfy.sh", + Topic: "diun-acce65a0-b777-46f9-9a11-58c67d1579c4", + Priority: 3, + Tags: []string{"package"}, + Timeout: utl.NewDuration(10 * time.Second), + TemplateTitle: model.NotifDefaultTemplateTitle, + TemplateBody: model.NotifDefaultTemplateBody, + }, Pushover: &model.NotifPushover{ Token: "uQiRzpo4DXghDmr9QzzfQu27cmVRsG", Recipient: "gznej3rKEVAvPUxu9vvNnqpmZpokzF", diff --git a/internal/config/fixtures/config.test.yml b/internal/config/fixtures/config.test.yml index 2fb4758d..a9c31851 100644 --- a/internal/config/fixtures/config.test.yml +++ b/internal/config/fixtures/config.test.yml @@ -63,6 +63,13 @@ notif: client: "diun" topic: "docker/diun" qos: 0 + ntfy: + endpoint: https://ntfy.sh + topic: diun-acce65a0-b777-46f9-9a11-58c67d1579c4 + priority: 3 + tags: + - package + timeout: 10s pushover: token: uQiRzpo4DXghDmr9QzzfQu27cmVRsG recipient: gznej3rKEVAvPUxu9vvNnqpmZpokzF diff --git a/internal/config/fixtures/config.validate.yml b/internal/config/fixtures/config.validate.yml index 8a582427..f36c59d9 100644 --- a/internal/config/fixtures/config.validate.yml +++ b/internal/config/fixtures/config.validate.yml @@ -61,6 +61,13 @@ notif: client: "diun" topic: "docker/diun" qos: 0 + ntfy: + endpoint: https://ntfy.sh + topic: diun-acce65a0-b777-46f9-9a11-58c67d1579c4 + priority: 3 + tags: + - package + timeout: 10s pushover: token: uQiRzpo4DXghDmr9QzzfQu27cmVRsG recipient: gznej3rKEVAvPUxu9vvNnqpmZpokzF diff --git a/internal/model/notif.go b/internal/model/notif.go index cca3d68e..37a897d7 100644 --- a/internal/model/notif.go +++ b/internal/model/notif.go @@ -38,6 +38,7 @@ type Notif struct { Mail *NotifMail `yaml:"mail,omitempty" json:"mail,omitempty"` Matrix *NotifMatrix `yaml:"matrix,omitempty" json:"matrix,omitempty"` Mqtt *NotifMqtt `yaml:"mqtt,omitempty" json:"mqtt,omitempty"` + Ntfy *NotifNtfy `yaml:"ntfy,omitempty" json:"ntfy,omitempty"` Pushover *NotifPushover `yaml:"pushover,omitempty" json:"pushover,omitempty"` RocketChat *NotifRocketChat `yaml:"rocketchat,omitempty" json:"rocketchat,omitempty"` Script *NotifScript `yaml:"script,omitempty" json:"script,omitempty"` diff --git a/internal/model/notif_ntfy.go b/internal/model/notif_ntfy.go new file mode 100644 index 00000000..f77f11ca --- /dev/null +++ b/internal/model/notif_ntfy.go @@ -0,0 +1,35 @@ +package model + +import ( + "time" + + "github.com/crazy-max/diun/v4/pkg/utl" +) + +// NotifNtfy holds ntfy notification configuration details +type NotifNtfy struct { + Endpoint string `yaml:"endpoint,omitempty" json:"endpoint,omitempty" validate:"required"` + Topic string `yaml:"topic,omitempty" json:"topic,omitempty" validate:"required"` + Priority int `yaml:"priority,omitempty" json:"priority,omitempty" validate:"omitempty,min=0"` + Tags []string `yaml:"tags,omitempty" json:"tags,omitempty" validate:"required"` + Timeout *time.Duration `yaml:"timeout,omitempty" json:"timeout,omitempty" validate:"required"` + TemplateTitle string `yaml:"templateTitle,omitempty" json:"templateTitle,omitempty" validate:"required"` + TemplateBody string `yaml:"templateBody,omitempty" json:"templateBody,omitempty" validate:"required"` +} + +// GetDefaults gets the default values +func (s *NotifNtfy) GetDefaults() *NotifNtfy { + n := &NotifNtfy{} + n.SetDefaults() + return n +} + +// SetDefaults sets the default values +func (s *NotifNtfy) SetDefaults() { + s.Endpoint = "https://ntfy.sh" + s.Priority = 3 + s.Tags = []string{"package"} + s.Timeout = utl.NewDuration(10 * time.Second) + s.TemplateTitle = NotifDefaultTemplateTitle + s.TemplateBody = NotifDefaultTemplateBody +} diff --git a/internal/notif/client.go b/internal/notif/client.go index fe03300c..4ddc915e 100644 --- a/internal/notif/client.go +++ b/internal/notif/client.go @@ -11,6 +11,7 @@ import ( "github.com/crazy-max/diun/v4/internal/notif/matrix" "github.com/crazy-max/diun/v4/internal/notif/mqtt" "github.com/crazy-max/diun/v4/internal/notif/notifier" + "github.com/crazy-max/diun/v4/internal/notif/ntfy" "github.com/crazy-max/diun/v4/internal/notif/pushover" "github.com/crazy-max/diun/v4/internal/notif/rocketchat" "github.com/crazy-max/diun/v4/internal/notif/script" @@ -61,6 +62,9 @@ func New(config *model.Notif, meta model.Meta) (*Client, error) { if config.Mqtt != nil { c.notifiers = append(c.notifiers, mqtt.New(config.Mqtt, meta)) } + if config.Ntfy != nil { + c.notifiers = append(c.notifiers, ntfy.New(config.Ntfy, meta)) + } if config.Pushover != nil { c.notifiers = append(c.notifiers, pushover.New(config.Pushover, meta)) } diff --git a/internal/notif/ntfy/client.go b/internal/notif/ntfy/client.go new file mode 100644 index 00000000..d23e30aa --- /dev/null +++ b/internal/notif/ntfy/client.go @@ -0,0 +1,110 @@ +package ntfy + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "net/url" + + "github.com/crazy-max/diun/v4/internal/model" + "github.com/crazy-max/diun/v4/internal/msg" + "github.com/crazy-max/diun/v4/internal/notif/notifier" +) + +// Client represents an active ntfy notification object +type Client struct { + *notifier.Notifier + cfg *model.NotifNtfy + meta model.Meta +} + +// New creates a new ntfy notification instance +func New(config *model.NotifNtfy, meta model.Meta) notifier.Notifier { + return notifier.Notifier{ + Handler: &Client{ + cfg: config, + meta: meta, + }, + } +} + +// Name returns notifier's name +func (c *Client) Name() string { + return "ntfy" +} + +// Send creates and sends a ntfy notification with an entry +func (c *Client) Send(entry model.NotifEntry) error { + hc := http.Client{ + Timeout: *c.cfg.Timeout, + } + + message, err := msg.New(msg.Options{ + Meta: c.meta, + Entry: entry, + TemplateTitle: c.cfg.TemplateTitle, + TemplateBody: c.cfg.TemplateBody, + }) + if err != nil { + return err + } + + title, body, err := message.RenderMarkdown() + if err != nil { + return err + } + + dataBuf := new(bytes.Buffer) + if err := json.NewEncoder(dataBuf).Encode(struct { + Topic string `json:"topic"` + Message string `json:"message"` + Title string `json:"title"` + Priority int `json:"priority"` + Tags []string `json:"tags"` + }{ + Topic: c.cfg.Topic, + Message: string(body), + Title: string(title), + Priority: c.cfg.Priority, + Tags: c.cfg.Tags, + }); err != nil { + return err + } + + u, err := url.Parse(c.cfg.Endpoint) + if err != nil { + return err + } + + q := u.Query() + u.RawQuery = q.Encode() + + req, err := http.NewRequest("POST", u.String(), dataBuf) + if err != nil { + return err + } + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("User-Agent", c.meta.UserAgent) + + resp, err := hc.Do(req) + if err != nil { + return err + } + + if resp.StatusCode != http.StatusOK { + var errBody struct { + Error string `json:"error"` + ErrorCode int `json:"errorCode"` + ErrorDescription string `json:"errorDescription"` + } + err := json.NewDecoder(resp.Body).Decode(&errBody) + if err != nil { + return err + } + return fmt.Errorf("%d %s: %s", errBody.ErrorCode, errBody.Error, errBody.ErrorDescription) + } + + return nil +} diff --git a/mkdocs.yml b/mkdocs.yml index db3a8eb9..6bc638fb 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -122,6 +122,7 @@ nav: - Mail: notif/mail.md - Matrix: notif/matrix.md - MQTT: notif/mqtt.md + - Ntfy: notif/ntfy.md - Pushover: notif/pushover.md - Rocket.Chat: notif/rocketchat.md - Script: notif/script.md