threema.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. package notifiers
  2. import (
  3. "fmt"
  4. "net/url"
  5. "strings"
  6. "github.com/grafana/grafana/pkg/bus"
  7. "github.com/grafana/grafana/pkg/log"
  8. "github.com/grafana/grafana/pkg/metrics"
  9. m "github.com/grafana/grafana/pkg/models"
  10. "github.com/grafana/grafana/pkg/services/alerting"
  11. )
  12. var (
  13. threemaGwBaseURL = "https://msgapi.threema.ch/%s"
  14. )
  15. func init() {
  16. alerting.RegisterNotifier(&alerting.NotifierPlugin{
  17. Type: "threema",
  18. Name: "Threema Gateway",
  19. Description: "Sends notifications to Threema using the Threema Gateway",
  20. Factory: NewThreemaNotifier,
  21. OptionsTemplate: `
  22. <h3 class="page-heading">Threema Gateway settings</h3>
  23. <p>
  24. Notifications can be configured for any Threema Gateway ID of type
  25. "Basic". End-to-End IDs are not currently supported.
  26. </p>
  27. <p>
  28. The Threema Gateway ID can be set up at
  29. <a href="https://gateway.threema.ch/" target="_blank" rel="noopener noreferrer">https://gateway.threema.ch/</a>.
  30. </p>
  31. <div class="gf-form">
  32. <span class="gf-form-label width-14">Gateway ID</span>
  33. <input type="text" required maxlength="8" pattern="\*[0-9A-Z]{7}"
  34. class="gf-form-input max-width-14"
  35. ng-model="ctrl.model.settings.gateway_id"
  36. placeholder="*3MAGWID">
  37. </input>
  38. <info-popover mode="right-normal">
  39. Your 8 character Threema Gateway ID (starting with a *)
  40. </info-popover>
  41. </div>
  42. <div class="gf-form">
  43. <span class="gf-form-label width-14">Recipient ID</span>
  44. <input type="text" required maxlength="8" pattern="[0-9A-Z]{8}"
  45. class="gf-form-input max-width-14"
  46. ng-model="ctrl.model.settings.recipient_id"
  47. placeholder="YOUR3MID">
  48. </input>
  49. <info-popover mode="right-normal">
  50. The 8 character Threema ID that should receive the alerts
  51. </info-popover>
  52. </div>
  53. <div class="gf-form">
  54. <span class="gf-form-label width-14">API Secret</span>
  55. <input type="text" required
  56. class="gf-form-input max-width-24"
  57. ng-model="ctrl.model.settings.api_secret">
  58. </input>
  59. <info-popover mode="right-normal">
  60. Your Threema Gateway API secret
  61. </info-popover>
  62. </div>
  63. `,
  64. })
  65. }
  66. type ThreemaNotifier struct {
  67. NotifierBase
  68. GatewayID string
  69. RecipientID string
  70. APISecret string
  71. log log.Logger
  72. }
  73. func NewThreemaNotifier(model *m.AlertNotification) (alerting.Notifier, error) {
  74. if model.Settings == nil {
  75. return nil, alerting.ValidationError{Reason: "No Settings Supplied"}
  76. }
  77. gatewayID := model.Settings.Get("gateway_id").MustString()
  78. recipientID := model.Settings.Get("recipient_id").MustString()
  79. apiSecret := model.Settings.Get("api_secret").MustString()
  80. // Validation
  81. if gatewayID == "" {
  82. return nil, alerting.ValidationError{Reason: "Could not find Threema Gateway ID in settings"}
  83. }
  84. if !strings.HasPrefix(gatewayID, "*") {
  85. return nil, alerting.ValidationError{Reason: "Invalid Threema Gateway ID: Must start with a *"}
  86. }
  87. if len(gatewayID) != 8 {
  88. return nil, alerting.ValidationError{Reason: "Invalid Threema Gateway ID: Must be 8 characters long"}
  89. }
  90. if recipientID == "" {
  91. return nil, alerting.ValidationError{Reason: "Could not find Threema Recipient ID in settings"}
  92. }
  93. if len(recipientID) != 8 {
  94. return nil, alerting.ValidationError{Reason: "Invalid Threema Recipient ID: Must be 8 characters long"}
  95. }
  96. if apiSecret == "" {
  97. return nil, alerting.ValidationError{Reason: "Could not find Threema API secret in settings"}
  98. }
  99. return &ThreemaNotifier{
  100. NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings),
  101. GatewayID: gatewayID,
  102. RecipientID: recipientID,
  103. APISecret: apiSecret,
  104. log: log.New("alerting.notifier.threema"),
  105. }, nil
  106. }
  107. func (notifier *ThreemaNotifier) Notify(evalContext *alerting.EvalContext) error {
  108. notifier.log.Info("Sending alert notification from", "threema_id", notifier.GatewayID)
  109. notifier.log.Info("Sending alert notification to", "threema_id", notifier.RecipientID)
  110. metrics.M_Alerting_Notification_Sent_Threema.Inc(1)
  111. // Set up basic API request data
  112. data := url.Values{}
  113. data.Set("from", notifier.GatewayID)
  114. data.Set("to", notifier.RecipientID)
  115. data.Set("secret", notifier.APISecret)
  116. // Determine emoji
  117. stateEmoji := ""
  118. switch evalContext.Rule.State {
  119. case m.AlertStateOK:
  120. stateEmoji = "\u2705 " // White Heavy Check Mark
  121. case m.AlertStateNoData:
  122. stateEmoji = "\u2753 " // Black Question Mark Ornament
  123. case m.AlertStateAlerting:
  124. stateEmoji = "\u26A0 " // Warning sign
  125. }
  126. // Build message
  127. message := fmt.Sprintf("%s%s\n\n*State:* %s\n*Message:* %s\n",
  128. stateEmoji, evalContext.GetNotificationTitle(),
  129. evalContext.Rule.Name, evalContext.Rule.Message)
  130. ruleURL, err := evalContext.GetRuleUrl()
  131. if err == nil {
  132. message = message + fmt.Sprintf("*URL:* %s\n", ruleURL)
  133. }
  134. if evalContext.ImagePublicUrl != "" {
  135. message = message + fmt.Sprintf("*Image:* %s\n", evalContext.ImagePublicUrl)
  136. }
  137. data.Set("text", message)
  138. // Prepare and send request
  139. url := fmt.Sprintf(threemaGwBaseURL, "send_simple")
  140. body := data.Encode()
  141. headers := map[string]string{
  142. "Content-Type": "application/x-www-form-urlencoded",
  143. }
  144. cmd := &m.SendWebhookSync{
  145. Url: url,
  146. Body: body,
  147. HttpMethod: "POST",
  148. HttpHeader: headers,
  149. }
  150. if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil {
  151. notifier.log.Error("Failed to send webhook", "error", err, "webhook", notifier.Name)
  152. return err
  153. }
  154. return nil
  155. }