Add Slack-Compatible webhook notification target
Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
parent
898a1b2276
commit
8af4ff08a3
6 changed files with 113 additions and 14 deletions
|
@ -20,7 +20,7 @@ Usage of birthday-notifier:
|
||||||
--fetch-interval duration How often to fetch birthdays from CardDAV (default 1h0m0s)
|
--fetch-interval duration How often to fetch birthdays from CardDAV (default 1h0m0s)
|
||||||
--log-level string Log level (debug, info, warn, error, fatal) (default "info")
|
--log-level string Log level (debug, info, warn, error, fatal) (default "info")
|
||||||
--notify-days-in-advance ints Send notification X days before birthday (default [1])
|
--notify-days-in-advance ints Send notification X days before birthday (default [1])
|
||||||
--notify-via string How to send the notification (one of: log, pushover) (default "log")
|
--notify-via strings How to send the notification (log, pushover, slack) (default [log])
|
||||||
--version Prints current version and exits
|
--version Prints current version and exits
|
||||||
--webdav-base-url string Webdav server to connect to
|
--webdav-base-url string Webdav server to connect to
|
||||||
--webdav-pass string Password for the Webdav user
|
--webdav-pass string Password for the Webdav user
|
||||||
|
@ -39,3 +39,8 @@ To adjust the notification text see the template in [`pkg/formatter/formatter.go
|
||||||
- `PUSHOVER_API_TOKEN` - Token for the App you've created in the Pushover Dashboard
|
- `PUSHOVER_API_TOKEN` - Token for the App you've created in the Pushover Dashboard
|
||||||
- `PUSHOVER_USER_KEY` - Token for the User to send the notification to
|
- `PUSHOVER_USER_KEY` - Token for the User to send the notification to
|
||||||
- `PUSHOVER_SOUND` - (Optional) Specify a sound to use
|
- `PUSHOVER_SOUND` - (Optional) Specify a sound to use
|
||||||
|
- **`slack`** - Send notification through Slack(-compatible) webhook
|
||||||
|
- `SLACK_WEBHOOK` - Webhook URL (i.e. `https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX` or `https://discord.com/api/webhooks/00000/XXXXX/slack`)
|
||||||
|
- `SLACK_CHANNEL` - (Optional) Specify the channel to send to
|
||||||
|
- `SLACK_ICON_EMOJI` - (Optional) Emoji to use as user icon
|
||||||
|
- `SLACK_USERNAME` - (Optional) Overwrite the hooks username
|
||||||
|
|
2
main.go
2
main.go
|
@ -21,7 +21,7 @@ var (
|
||||||
FetchInterval time.Duration `flag:"fetch-interval" default:"1h" description:"How often to fetch birthdays from CardDAV"`
|
FetchInterval time.Duration `flag:"fetch-interval" default:"1h" description:"How often to fetch birthdays from CardDAV"`
|
||||||
LogLevel string `flag:"log-level" default:"info" description:"Log level (debug, info, warn, error, fatal)"`
|
LogLevel string `flag:"log-level" default:"info" description:"Log level (debug, info, warn, error, fatal)"`
|
||||||
NotifyDaysInAdvance []int `flag:"notify-days-in-advance" default:"1" description:"Send notification X days before birthday"`
|
NotifyDaysInAdvance []int `flag:"notify-days-in-advance" default:"1" description:"Send notification X days before birthday"`
|
||||||
NotifyVia []string `flag:"notify-via" default:"log" description:"How to send the notification (one of: log, pushover)"`
|
NotifyVia []string `flag:"notify-via" default:"log" description:"How to send the notification (log, pushover, slack)"`
|
||||||
WebdavBaseURL string `flag:"webdav-base-url" default:"" description:"Webdav server to connect to"`
|
WebdavBaseURL string `flag:"webdav-base-url" default:"" description:"Webdav server to connect to"`
|
||||||
WebdavPass string `flag:"webdav-pass" default:"" description:"Password for the Webdav user"`
|
WebdavPass string `flag:"webdav-pass" default:"" description:"Password for the Webdav user"`
|
||||||
WebdavPrincipal string `flag:"webdav-principal" default:"principals/users/%s" description:"Principal format to fetch the addressbooks for (%s will be replaced with the webdav-user)"`
|
WebdavPrincipal string `flag:"webdav-principal" default:"principals/users/%s" description:"Principal format to fetch the addressbooks for (%s will be replaced with the webdav-user)"`
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"git.luzifer.io/luzifer/birthday-notifier/pkg/notifier"
|
"git.luzifer.io/luzifer/birthday-notifier/pkg/notifier"
|
||||||
"git.luzifer.io/luzifer/birthday-notifier/pkg/notifier/log"
|
"git.luzifer.io/luzifer/birthday-notifier/pkg/notifier/log"
|
||||||
"git.luzifer.io/luzifer/birthday-notifier/pkg/notifier/pushover"
|
"git.luzifer.io/luzifer/birthday-notifier/pkg/notifier/pushover"
|
||||||
|
"git.luzifer.io/luzifer/birthday-notifier/pkg/notifier/slack"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getNotifierByName(name string) notifier.Notifier {
|
func getNotifierByName(name string) notifier.Notifier {
|
||||||
|
@ -14,6 +15,9 @@ func getNotifierByName(name string) notifier.Notifier {
|
||||||
case "pushover":
|
case "pushover":
|
||||||
return pushover.Notifier{}
|
return pushover.Notifier{}
|
||||||
|
|
||||||
|
case "slack":
|
||||||
|
return slack.Notifier{}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,6 +59,22 @@ func FormatNotificationText(contact vcard.Card, when time.Time) (text string, er
|
||||||
return buf.String(), nil
|
return buf.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FormatNotificationTitle provides a title from the contacts formatted
|
||||||
|
// name or from given and family name
|
||||||
|
func FormatNotificationTitle(contact vcard.Card) (title string) {
|
||||||
|
for _, fn := range contact.FormattedNames() {
|
||||||
|
if fn.Value != "" {
|
||||||
|
title = fmt.Sprintf("%s (Birthday)", fn.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if title == "" {
|
||||||
|
title = fmt.Sprintf("%s %s (Birthday)", contact.Name().GivenName, contact.Name().FamilyName)
|
||||||
|
}
|
||||||
|
|
||||||
|
return title
|
||||||
|
}
|
||||||
|
|
||||||
func getAge(t time.Time) int {
|
func getAge(t time.Time) int {
|
||||||
return dateutil.ProjectToNextBirthday(t).Year() - t.Year()
|
return dateutil.ProjectToNextBirthday(t).Year() - t.Year()
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,20 +43,9 @@ func (Notifier) SendNotification(contact vcard.Card, when time.Time) error {
|
||||||
return fmt.Errorf("rendering text: %w", err)
|
return fmt.Errorf("rendering text: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var title string
|
|
||||||
for _, fn := range contact.FormattedNames() {
|
|
||||||
if fn.Value != "" {
|
|
||||||
title = fmt.Sprintf("%s (Birthday)", fn.Value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if title == "" {
|
|
||||||
title = fmt.Sprintf("%s %s (Birthday)", contact.Name().GivenName, contact.Name().FamilyName)
|
|
||||||
}
|
|
||||||
|
|
||||||
message := &pushover.Message{
|
message := &pushover.Message{
|
||||||
Message: text,
|
Message: text,
|
||||||
Title: title,
|
Title: formatter.FormatNotificationTitle(contact),
|
||||||
Sound: os.Getenv("PUSHOVER_SOUND"),
|
Sound: os.Getenv("PUSHOVER_SOUND"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
85
pkg/notifier/slack/slack.go
Normal file
85
pkg/notifier/slack/slack.go
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
// Package slack provides a notifier to send birthday notifications
|
||||||
|
// through a Slack(-compatible) WebHook
|
||||||
|
package slack
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.luzifer.io/luzifer/birthday-notifier/pkg/formatter"
|
||||||
|
"git.luzifer.io/luzifer/birthday-notifier/pkg/notifier"
|
||||||
|
"github.com/emersion/go-vcard"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
const webhookPostTimeout = 2 * time.Second
|
||||||
|
|
||||||
|
type (
|
||||||
|
// Notifier implements the notifier interface
|
||||||
|
Notifier struct{}
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ notifier.Notifier = Notifier{}
|
||||||
|
|
||||||
|
// SendNotification implements the Notifier interface
|
||||||
|
func (Notifier) SendNotification(contact vcard.Card, when time.Time) error {
|
||||||
|
if contact.Name() == nil {
|
||||||
|
return fmt.Errorf("contact has no name")
|
||||||
|
}
|
||||||
|
|
||||||
|
webhookURL := os.Getenv("SLACK_WEBHOOK")
|
||||||
|
|
||||||
|
if webhookURL == "" {
|
||||||
|
return fmt.Errorf("missing SLACK_WEBHOOK env variable")
|
||||||
|
}
|
||||||
|
|
||||||
|
text, err := formatter.FormatNotificationText(contact, when)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("rendering text: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
payload := new(bytes.Buffer)
|
||||||
|
if err = json.NewEncoder(payload).Encode(struct {
|
||||||
|
Channel string `json:"channel,omitempty"`
|
||||||
|
IconEmoji string `json:"icon_emoji,omitempty"`
|
||||||
|
Text string `json:"text"`
|
||||||
|
Username string `json:"username,omitempty"`
|
||||||
|
}{
|
||||||
|
Channel: os.Getenv("SLACK_CHANNEL"),
|
||||||
|
IconEmoji: os.Getenv("SLACK_ICON_EMOJI"),
|
||||||
|
Text: text,
|
||||||
|
Username: os.Getenv("SLACK_USERNAME"),
|
||||||
|
}); err != nil {
|
||||||
|
return fmt.Errorf("encoding hook payload: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), webhookPostTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, webhookURL, payload)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("creating request: %w", err)
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("executing request: %w", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err := resp.Body.Close(); err != nil {
|
||||||
|
logrus.WithError(err).Error("closing slack response body (leaked fd)")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return fmt.Errorf("unexpected status %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in a new issue