From a1a9f339bf63cd2208484530c9e2caa6e2d9d10c Mon Sep 17 00:00:00 2001 From: Robin Burchell Date: Wed, 22 Nov 2017 10:40:16 -0500 Subject: [PATCH 1/3] telegram: Fix a typo in variable name --- pkg/services/alerting/notifiers/telegram.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/services/alerting/notifiers/telegram.go b/pkg/services/alerting/notifiers/telegram.go index 62da584d019..c856cc9da43 100644 --- a/pkg/services/alerting/notifiers/telegram.go +++ b/pkg/services/alerting/notifiers/telegram.go @@ -11,7 +11,7 @@ import ( ) var ( - telegeramApiUrl string = "https://api.telegram.org/bot%s/%s" + telegramApiUrl string = "https://api.telegram.org/bot%s/%s" ) func init() { @@ -113,7 +113,7 @@ func (this *TelegramNotifier) Notify(evalContext *alerting.EvalContext) error { bodyJSON.Set("text", message) - url := fmt.Sprintf(telegeramApiUrl, this.BotToken, "sendMessage") + url := fmt.Sprintf(telegramApiUrl, this.BotToken, "sendMessage") body, _ := bodyJSON.MarshalJSON() cmd := &m.SendWebhookSync{ From b6fcf2b6091d5ca341102c023dd61a8a98b7a255 Mon Sep 17 00:00:00 2001 From: Robin Burchell Date: Wed, 22 Nov 2017 10:40:42 -0500 Subject: [PATCH 2/3] telegram: Switch to using multipart form rather than JSON as a body This is a pre-requisite for uploading inline images. --- pkg/services/alerting/notifiers/telegram.go | 29 +++++++++++++-------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/pkg/services/alerting/notifiers/telegram.go b/pkg/services/alerting/notifiers/telegram.go index c856cc9da43..7956ebaf014 100644 --- a/pkg/services/alerting/notifiers/telegram.go +++ b/pkg/services/alerting/notifiers/telegram.go @@ -1,13 +1,13 @@ package notifiers import ( + "bytes" "fmt" - "github.com/grafana/grafana/pkg/bus" - "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/log" m "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/alerting" + "mime/multipart" ) var ( @@ -84,11 +84,6 @@ func (this *TelegramNotifier) Notify(evalContext *alerting.EvalContext) error { this.log.Info("Sending alert notification to", "bot_token", this.BotToken) this.log.Info("Sending alert notification to", "chat_id", this.ChatID) - bodyJSON := simplejson.New() - - bodyJSON.Set("chat_id", this.ChatID) - bodyJSON.Set("parse_mode", "html") - message := fmt.Sprintf("%s\nState: %s\nMessage: %s\n", evalContext.GetNotificationTitle(), evalContext.Rule.Name, evalContext.Rule.Message) ruleUrl, err := evalContext.GetRuleUrl() @@ -111,15 +106,27 @@ func (this *TelegramNotifier) Notify(evalContext *alerting.EvalContext) error { message = message + fmt.Sprintf("\nMetrics:%s", metrics) } - bodyJSON.Set("text", message) + var body bytes.Buffer + w := multipart.NewWriter(&body) + fw, _ := w.CreateFormField("chat_id") + fw.Write([]byte(this.ChatID)) - url := fmt.Sprintf(telegramApiUrl, this.BotToken, "sendMessage") - body, _ := bodyJSON.MarshalJSON() + fw, _ = w.CreateFormField("parse_mode") + fw.Write([]byte("html")) + fw, _ = w.CreateFormField("text") + fw.Write([]byte(message)) + + w.Close() + + url := fmt.Sprintf(telegramApiUrl, this.BotToken, "sendMessage") cmd := &m.SendWebhookSync{ Url: url, - Body: string(body), + Body: body.String(), HttpMethod: "POST", + HttpHeader: map[string]string{ + "Content-Type": w.FormDataContentType(), + }, } if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil { From 43109dc8cad2c4759a882c0b1c87b1934c850af7 Mon Sep 17 00:00:00 2001 From: Robin Burchell Date: Wed, 22 Nov 2017 10:41:36 -0500 Subject: [PATCH 3/3] telegram: Send notifications with an inline image If image upload is enabled, but there is no public image repository, fall back to sending an image via telegram's API. --- pkg/services/alerting/notifiers/telegram.go | 94 +++++++++++++++++---- 1 file changed, 76 insertions(+), 18 deletions(-) diff --git a/pkg/services/alerting/notifiers/telegram.go b/pkg/services/alerting/notifiers/telegram.go index 7956ebaf014..0ca1ad3dfe0 100644 --- a/pkg/services/alerting/notifiers/telegram.go +++ b/pkg/services/alerting/notifiers/telegram.go @@ -7,7 +7,9 @@ import ( "github.com/grafana/grafana/pkg/log" m "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/alerting" + "io" "mime/multipart" + "os" ) var ( @@ -47,9 +49,10 @@ func init() { type TelegramNotifier struct { NotifierBase - BotToken string - ChatID string - log log.Logger + BotToken string + ChatID string + UploadImage bool + log log.Logger } func NewTelegramNotifier(model *m.AlertNotification) (alerting.Notifier, error) { @@ -59,6 +62,7 @@ func NewTelegramNotifier(model *m.AlertNotification) (alerting.Notifier, error) botToken := model.Settings.Get("bottoken").MustString() chatId := model.Settings.Get("chatid").MustString() + uploadImage := model.Settings.Get("uploadImage").MustBool() if botToken == "" { return nil, alerting.ValidationError{Reason: "Could not find Bot Token in settings"} @@ -72,26 +76,42 @@ func NewTelegramNotifier(model *m.AlertNotification) (alerting.Notifier, error) NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings), BotToken: botToken, ChatID: chatId, + UploadImage: uploadImage, log: log.New("alerting.notifier.telegram"), }, nil } -func (this *TelegramNotifier) ShouldNotify(context *alerting.EvalContext) bool { - return defaultShouldNotify(context) -} +func (this *TelegramNotifier) buildMessage(evalContext *alerting.EvalContext, sendImageInline bool) *m.SendWebhookSync { + var imageFile *os.File + var err error -func (this *TelegramNotifier) Notify(evalContext *alerting.EvalContext) error { - this.log.Info("Sending alert notification to", "bot_token", this.BotToken) - this.log.Info("Sending alert notification to", "chat_id", this.ChatID) + if sendImageInline { + imageFile, err = os.Open(evalContext.ImageOnDiskPath) + defer imageFile.Close() + if err != nil { + sendImageInline = false // fall back to text message + } + } - message := fmt.Sprintf("%s\nState: %s\nMessage: %s\n", evalContext.GetNotificationTitle(), evalContext.Rule.Name, evalContext.Rule.Message) + message := "" + + if sendImageInline { + // Telegram's API does not allow HTML formatting for image captions. + message = fmt.Sprintf("%s\nState: %s\nMessage: %s\n", evalContext.GetNotificationTitle(), evalContext.Rule.Name, evalContext.Rule.Message) + } else { + message = fmt.Sprintf("%s\nState: %s\nMessage: %s\n", evalContext.GetNotificationTitle(), evalContext.Rule.Name, evalContext.Rule.Message) + } ruleUrl, err := evalContext.GetRuleUrl() if err == nil { message = message + fmt.Sprintf("URL: %s\n", ruleUrl) } - if evalContext.ImagePublicUrl != "" { - message = message + fmt.Sprintf("Image: %s\n", evalContext.ImagePublicUrl) + + if !sendImageInline { + // only attach this if we are not sending it inline. + if evalContext.ImagePublicUrl != "" { + message = message + fmt.Sprintf("Image: %s\n", evalContext.ImagePublicUrl) + } } metrics := "" @@ -102,24 +122,48 @@ func (this *TelegramNotifier) Notify(evalContext *alerting.EvalContext) error { break } } + if metrics != "" { - message = message + fmt.Sprintf("\nMetrics:%s", metrics) + if sendImageInline { + // Telegram's API does not allow HTML formatting for image captions. + message = message + fmt.Sprintf("\nMetrics:%s", metrics) + } else { + message = message + fmt.Sprintf("\nMetrics:%s", metrics) + } } var body bytes.Buffer + w := multipart.NewWriter(&body) fw, _ := w.CreateFormField("chat_id") fw.Write([]byte(this.ChatID)) - fw, _ = w.CreateFormField("parse_mode") - fw.Write([]byte("html")) + if sendImageInline { + fw, _ = w.CreateFormField("caption") + fw.Write([]byte(message)) - fw, _ = w.CreateFormField("text") - fw.Write([]byte(message)) + fw, _ = w.CreateFormFile("photo", evalContext.ImageOnDiskPath) + io.Copy(fw, imageFile) + } else { + fw, _ = w.CreateFormField("text") + fw.Write([]byte(message)) + + fw, _ = w.CreateFormField("parse_mode") + fw.Write([]byte("html")) + } w.Close() - url := fmt.Sprintf(telegramApiUrl, this.BotToken, "sendMessage") + apiMethod := "" + if sendImageInline { + this.log.Info("Sending telegram image notification", "photo", evalContext.ImageOnDiskPath, "chat_id", this.ChatID, "bot_token", this.BotToken) + apiMethod = "sendPhoto" + } else { + this.log.Info("Sending telegram text notification", "chat_id", this.ChatID, "bot_token", this.BotToken) + apiMethod = "sendMessage" + } + + url := fmt.Sprintf(telegramApiUrl, this.BotToken, apiMethod) cmd := &m.SendWebhookSync{ Url: url, Body: body.String(), @@ -128,6 +172,20 @@ func (this *TelegramNotifier) Notify(evalContext *alerting.EvalContext) error { "Content-Type": w.FormDataContentType(), }, } + return cmd +} + +func (this *TelegramNotifier) ShouldNotify(context *alerting.EvalContext) bool { + return defaultShouldNotify(context) +} + +func (this *TelegramNotifier) Notify(evalContext *alerting.EvalContext) error { + var cmd *m.SendWebhookSync + if evalContext.ImagePublicUrl == "" && this.UploadImage == true { + cmd = this.buildMessage(evalContext, true) + } else { + cmd = this.buildMessage(evalContext, false) + } if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil { this.log.Error("Failed to send webhook", "error", err, "webhook", this.Name)