diff --git a/.res/notif-telegram.png b/.res/notif-telegram.png new file mode 100644 index 00000000..3442f036 Binary files /dev/null and b/.res/notif-telegram.png differ diff --git a/doc/configuration.md b/doc/configuration.md index bed16d3b..147b35cb 100644 --- a/doc/configuration.md +++ b/doc/configuration.md @@ -25,6 +25,12 @@ notif: slack: enable: false webhook_url: https://hooks.slack.com/services/ABCD12EFG/HIJK34LMN/01234567890abcdefghij + telegram: + enable: false + token: aabbccdd:11223344 + chat_ids: + - 123456789 + - 987654321 webhook: enable: false endpoint: http://webhook.foo.com/sd54qad89azd5a @@ -116,6 +122,11 @@ providers: * `enable`: Enable slack notification (default: `false`). * `webhook_url`: Slack [incoming webhook URL](https://api.slack.com/messaging/webhooks). **required** +* `telegram` + * `enable`: Enable Telegram notification (default: `false`). + * `token`: Telegram bot token. **required** + * `chat_ids`: List of chat IDs to send notifications to. **required** + * `webhook` * `enable`: Enable webhook notification (default: `false`). * `endpoint`: URL of the HTTP request. **required** diff --git a/doc/notifications.md b/doc/notifications.md index 16b58ad5..934cf08b 100644 --- a/doc/notifications.md +++ b/doc/notifications.md @@ -2,6 +2,7 @@ * [Mail](#mail) * [Slack](#slack) +* [Telegram](#telegram) * [Webhook](#webhook) ## Mail @@ -16,6 +17,17 @@ You can send notifications to your slack channel using an [incoming webhook URL] ![](../.res/notif-slack.png) +## Telegram + +Notifications can be sent via Telegram using a [Telegram Bot](https://core.telegram.org/bots). + +Follow the [instructions](https://core.telegram.org/bots#6-botfather) to set up a bot and get it's token. + +Message the [GetID bot](https://t.me/getidsbot) to find your chat ID. +Multiple chat IDs can be provided in order to deliver notifications to multiple recipients. + +![](../.res/notif-telegram.png) + ## Webhook If you choose `webhook` notification, a HTTP request is sent with a JSON format response that looks like: diff --git a/go.mod b/go.mod index 6bf7e882..e52b47f8 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect github.com/ghodss/yaml v1.0.0 // indirect github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df + github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible github.com/gogo/protobuf v1.3.1 // indirect github.com/google/go-cmp v0.3.1 // indirect github.com/gorilla/mux v1.7.3 // indirect @@ -33,6 +34,7 @@ require ( github.com/rs/zerolog v1.17.2 github.com/sirupsen/logrus v1.4.2 github.com/stretchr/testify v1.4.0 + github.com/technoweenie/multipartstreamer v1.0.1 // indirect go.etcd.io/bbolt v1.3.3 golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect google.golang.org/grpc v1.25.1 // indirect diff --git a/go.sum b/go.sum index 44280ca0..b9d01ae5 100644 --- a/go.sum +++ b/go.sum @@ -71,6 +71,8 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible h1:2cauKuaELYAEARXRkq2LrJ0yDDv1rW7+wrTEdVL3uaU= +github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM= github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= @@ -203,6 +205,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/tchap/go-patricia v2.3.0+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I= +github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM= +github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog= github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/vbatts/tar-split v0.11.1/go.mod h1:LEuURwDEiWjRjwu46yU3KVGuUdVv/dcnpcEPSzR8z6g= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= diff --git a/internal/config/config.go b/internal/config/config.go index e2e31eed..0db7d95f 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -59,6 +59,9 @@ func Load(flags model.Flags, version string) (*Config, error) { Slack: model.NotifSlack{ Enable: false, }, + Telegram: model.NotifTelegram{ + Enable: false, + }, Webhook: model.NotifWebhook{ Enable: false, Method: "GET", diff --git a/internal/config/config.test.yml b/internal/config/config.test.yml index 0745b7c3..c03cd6c0 100644 --- a/internal/config/config.test.yml +++ b/internal/config/config.test.yml @@ -22,6 +22,12 @@ notif: slack: enable: false webhook_url: https://hooks.slack.com/services/ABCD12EFG/HIJK34LMN/01234567890abcdefghij + telegram: + enable: false + token: abcdef123456 + chat_ids: + - 8547439 + - 1234567 webhook: enable: false endpoint: http://webhook.foo.com/sd54qad89azd5a diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 7c31ae87..8744e45b 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -63,6 +63,11 @@ func TestLoad(t *testing.T) { Enable: false, WebhookURL: "https://hooks.slack.com/services/ABCD12EFG/HIJK34LMN/01234567890abcdefghij", }, + Telegram: model.NotifTelegram{ + Enable: false, + BotToken: "abcdef123456", + ChatIDs: []int64{8547439, 1234567}, + }, Webhook: model.NotifWebhook{ Enable: false, Endpoint: "http://webhook.foo.com/sd54qad89azd5a", diff --git a/internal/model/notif.go b/internal/model/notif.go index 80714e27..78fe826a 100644 --- a/internal/model/notif.go +++ b/internal/model/notif.go @@ -14,9 +14,10 @@ type NotifEntry struct { // Notif holds data necessary for notification configuration type Notif struct { - Mail NotifMail `yaml:"mail,omitempty"` - Slack NotifSlack `yaml:"slack,omitempty"` - Webhook NotifWebhook `yaml:"webhook,omitempty"` + Mail NotifMail `yaml:"mail,omitempty"` + Slack NotifSlack `yaml:"slack,omitempty"` + Telegram NotifTelegram `yaml:"telegram,omitempty"` + Webhook NotifWebhook `yaml:"webhook,omitempty"` } // NotifMail holds mail notification configuration details @@ -40,6 +41,13 @@ type NotifSlack struct { WebhookURL string `yaml:"webhook_url,omitempty"` } +// NotifTelegram holds Telegram notification configuration details +type NotifTelegram struct { + Enable bool `yaml:"enable,omitempty"` + BotToken string `yaml:"token,omitempty"` + ChatIDs []int64 `yaml:"chat_ids,omitempty"` +} + // NotifWebhook holds webhook notification configuration details type NotifWebhook struct { Enable bool `yaml:"enable,omitempty"` diff --git a/internal/notif/client.go b/internal/notif/client.go index a53ce0a1..8f17d73c 100644 --- a/internal/notif/client.go +++ b/internal/notif/client.go @@ -5,6 +5,7 @@ import ( "github.com/crazy-max/diun/internal/notif/mail" "github.com/crazy-max/diun/internal/notif/notifier" "github.com/crazy-max/diun/internal/notif/slack" + "github.com/crazy-max/diun/internal/notif/telegram" "github.com/crazy-max/diun/internal/notif/webhook" "github.com/rs/zerolog/log" ) @@ -31,6 +32,9 @@ func New(config model.Notif, app model.App) (*Client, error) { if config.Slack.Enable { c.notifiers = append(c.notifiers, slack.New(config.Slack, app)) } + if config.Telegram.Enable { + c.notifiers = append(c.notifiers, telegram.New(config.Telegram, app)) + } if config.Webhook.Enable { c.notifiers = append(c.notifiers, webhook.New(config.Webhook, app)) } diff --git a/internal/notif/telegram/telegram.go b/internal/notif/telegram/telegram.go new file mode 100644 index 00000000..a0fb66c6 --- /dev/null +++ b/internal/notif/telegram/telegram.go @@ -0,0 +1,63 @@ +package telegram + +import ( + "bytes" + "errors" + "text/template" + + "github.com/crazy-max/diun/internal/model" + "github.com/crazy-max/diun/internal/notif/notifier" + "github.com/go-telegram-bot-api/telegram-bot-api" + "github.com/rs/zerolog/log" +) + +// Client represents an active Telegram notification object +type Client struct { + *notifier.Notifier + cfg model.NotifTelegram + app model.App + bot *tgbotapi.BotAPI +} + +// New creates a new Telegram notification instance +func New(config model.NotifTelegram, app model.App) notifier.Notifier { + bot, err := tgbotapi.NewBotAPI(config.BotToken) + if err != nil { + log.Err(err).Msgf("Failed to initialize Telegram notifications") + } + return notifier.Notifier{ + Handler: &Client{ + cfg: config, + app: app, + bot: bot, + }, + } +} + +// Name returns notifier's name +func (c *Client) Name() string { + return "telegram" +} + +// Send creates and sends a Telegram notification with an entry +func (c *Client) Send(entry model.NotifEntry) error { + if c.bot == nil { + return errors.New("Telegram not initialized") + } + + var msgBuf bytes.Buffer + msgTpl := template.Must(template.New("email").Parse(`Docker 🐳 tag {{ .Image.Domain }}/{{ .Image.Path }}:{{ .Image.Tag }} which you subscribed to through {{ .Provider }} provider has been {{ if (eq .Status "new") }}newly added{{ else }}updated{{ end }}.`)) + if err := msgTpl.Execute(&msgBuf, entry); err != nil { + return err + } + + for _, chatID := range c.cfg.ChatIDs { + msg := tgbotapi.NewMessage(chatID, c.bot.Self.UserName) + msg.Text = msgBuf.String() + if _, err := c.bot.Send(msg); err != nil { + return err + } + } + + return nil +}