telegram.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. package notifiers
  2. import (
  3. "bytes"
  4. "fmt"
  5. "github.com/grafana/grafana/pkg/bus"
  6. "github.com/grafana/grafana/pkg/log"
  7. m "github.com/grafana/grafana/pkg/models"
  8. "github.com/grafana/grafana/pkg/services/alerting"
  9. "io"
  10. "mime/multipart"
  11. "os"
  12. )
  13. var (
  14. telegramApiUrl string = "https://api.telegram.org/bot%s/%s"
  15. )
  16. func init() {
  17. alerting.RegisterNotifier(&alerting.NotifierPlugin{
  18. Type: "telegram",
  19. Name: "Telegram",
  20. Description: "Sends notifications to Telegram",
  21. Factory: NewTelegramNotifier,
  22. OptionsTemplate: `
  23. <h3 class="page-heading">Telegram API settings</h3>
  24. <div class="gf-form">
  25. <span class="gf-form-label width-9">BOT API Token</span>
  26. <input type="text" required
  27. class="gf-form-input"
  28. ng-model="ctrl.model.settings.bottoken"
  29. placeholder="Telegram BOT API Token"></input>
  30. </div>
  31. <div class="gf-form">
  32. <span class="gf-form-label width-9">Chat ID</span>
  33. <input type="text" required
  34. class="gf-form-input"
  35. ng-model="ctrl.model.settings.chatid"
  36. data-placement="right">
  37. </input>
  38. <info-popover mode="right-absolute">
  39. Integer Telegram Chat Identifier
  40. </info-popover>
  41. </div>
  42. `,
  43. })
  44. }
  45. type TelegramNotifier struct {
  46. NotifierBase
  47. BotToken string
  48. ChatID string
  49. UploadImage bool
  50. log log.Logger
  51. }
  52. func NewTelegramNotifier(model *m.AlertNotification) (alerting.Notifier, error) {
  53. if model.Settings == nil {
  54. return nil, alerting.ValidationError{Reason: "No Settings Supplied"}
  55. }
  56. botToken := model.Settings.Get("bottoken").MustString()
  57. chatId := model.Settings.Get("chatid").MustString()
  58. uploadImage := model.Settings.Get("uploadImage").MustBool()
  59. if botToken == "" {
  60. return nil, alerting.ValidationError{Reason: "Could not find Bot Token in settings"}
  61. }
  62. if chatId == "" {
  63. return nil, alerting.ValidationError{Reason: "Could not find Chat Id in settings"}
  64. }
  65. return &TelegramNotifier{
  66. NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings),
  67. BotToken: botToken,
  68. ChatID: chatId,
  69. UploadImage: uploadImage,
  70. log: log.New("alerting.notifier.telegram"),
  71. }, nil
  72. }
  73. func (this *TelegramNotifier) buildMessage(evalContext *alerting.EvalContext, sendImageInline bool) *m.SendWebhookSync {
  74. var imageFile *os.File
  75. var err error
  76. if sendImageInline {
  77. imageFile, err = os.Open(evalContext.ImageOnDiskPath)
  78. defer imageFile.Close()
  79. if err != nil {
  80. sendImageInline = false // fall back to text message
  81. }
  82. }
  83. message := ""
  84. if sendImageInline {
  85. // Telegram's API does not allow HTML formatting for image captions.
  86. message = fmt.Sprintf("%s\nState: %s\nMessage: %s\n", evalContext.GetNotificationTitle(), evalContext.Rule.Name, evalContext.Rule.Message)
  87. } else {
  88. message = fmt.Sprintf("<b>%s</b>\nState: %s\nMessage: %s\n", evalContext.GetNotificationTitle(), evalContext.Rule.Name, evalContext.Rule.Message)
  89. }
  90. ruleUrl, err := evalContext.GetRuleUrl()
  91. if err == nil {
  92. message = message + fmt.Sprintf("URL: %s\n", ruleUrl)
  93. }
  94. if !sendImageInline {
  95. // only attach this if we are not sending it inline.
  96. if evalContext.ImagePublicUrl != "" {
  97. message = message + fmt.Sprintf("Image: %s\n", evalContext.ImagePublicUrl)
  98. }
  99. }
  100. metrics := ""
  101. fieldLimitCount := 4
  102. for index, evt := range evalContext.EvalMatches {
  103. metrics += fmt.Sprintf("\n%s: %s", evt.Metric, evt.Value)
  104. if index > fieldLimitCount {
  105. break
  106. }
  107. }
  108. if metrics != "" {
  109. if sendImageInline {
  110. // Telegram's API does not allow HTML formatting for image captions.
  111. message = message + fmt.Sprintf("\nMetrics:%s", metrics)
  112. } else {
  113. message = message + fmt.Sprintf("\n<i>Metrics:</i>%s", metrics)
  114. }
  115. }
  116. var body bytes.Buffer
  117. w := multipart.NewWriter(&body)
  118. fw, _ := w.CreateFormField("chat_id")
  119. fw.Write([]byte(this.ChatID))
  120. if sendImageInline {
  121. fw, _ = w.CreateFormField("caption")
  122. fw.Write([]byte(message))
  123. fw, _ = w.CreateFormFile("photo", evalContext.ImageOnDiskPath)
  124. io.Copy(fw, imageFile)
  125. } else {
  126. fw, _ = w.CreateFormField("text")
  127. fw.Write([]byte(message))
  128. fw, _ = w.CreateFormField("parse_mode")
  129. fw.Write([]byte("html"))
  130. }
  131. w.Close()
  132. apiMethod := ""
  133. if sendImageInline {
  134. this.log.Info("Sending telegram image notification", "photo", evalContext.ImageOnDiskPath, "chat_id", this.ChatID, "bot_token", this.BotToken)
  135. apiMethod = "sendPhoto"
  136. } else {
  137. this.log.Info("Sending telegram text notification", "chat_id", this.ChatID, "bot_token", this.BotToken)
  138. apiMethod = "sendMessage"
  139. }
  140. url := fmt.Sprintf(telegramApiUrl, this.BotToken, apiMethod)
  141. cmd := &m.SendWebhookSync{
  142. Url: url,
  143. Body: body.String(),
  144. HttpMethod: "POST",
  145. HttpHeader: map[string]string{
  146. "Content-Type": w.FormDataContentType(),
  147. },
  148. }
  149. return cmd
  150. }
  151. func (this *TelegramNotifier) ShouldNotify(context *alerting.EvalContext) bool {
  152. return defaultShouldNotify(context)
  153. }
  154. func (this *TelegramNotifier) Notify(evalContext *alerting.EvalContext) error {
  155. var cmd *m.SendWebhookSync
  156. if evalContext.ImagePublicUrl == "" && this.UploadImage == true {
  157. cmd = this.buildMessage(evalContext, true)
  158. } else {
  159. cmd = this.buildMessage(evalContext, false)
  160. }
  161. if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil {
  162. this.log.Error("Failed to send webhook", "error", err, "webhook", this.Name)
  163. return err
  164. }
  165. return nil
  166. }