2021-03-31 01:38:34 +02:00
package telegram
import (
2021-12-03 03:05:17 +01:00
"bytes"
2022-10-20 21:22:27 +02:00
"encoding/json"
2024-12-17 02:32:13 +01:00
"errors"
2021-03-31 01:38:34 +02:00
"fmt"
2021-12-03 07:44:17 +01:00
"io"
2021-03-31 01:38:34 +02:00
"net/http"
2022-12-06 07:41:09 +01:00
"github.com/TwiN/gatus/v5/alerting/alert"
"github.com/TwiN/gatus/v5/client"
2024-05-10 04:56:16 +02:00
"github.com/TwiN/gatus/v5/config/endpoint"
2024-12-17 02:32:13 +01:00
"gopkg.in/yaml.v3"
2021-03-31 01:38:34 +02:00
)
2024-12-17 02:32:13 +01:00
const defaultApiUrl = "https://api.telegram.org"
2022-03-05 21:44:11 +01:00
2024-12-17 02:32:13 +01:00
var (
ErrTokenNotSet = errors . New ( "token not set" )
ErrIDNotSet = errors . New ( "id not set" )
ErrDuplicateGroupOverride = errors . New ( "duplicate group override" )
)
type Config struct {
2022-03-05 21:44:11 +01:00
Token string ` yaml:"token" `
ID string ` yaml:"id" `
2024-12-17 02:32:13 +01:00
ApiUrl string ` yaml:"api-url" `
2021-05-16 03:31:32 +02:00
2022-09-08 00:50:59 +02:00
ClientConfig * client . Config ` yaml:"client,omitempty" `
2024-12-17 02:32:13 +01:00
}
func ( cfg * Config ) Validate ( ) error {
if len ( cfg . ApiUrl ) == 0 {
cfg . ApiUrl = defaultApiUrl
}
if len ( cfg . Token ) == 0 {
return ErrTokenNotSet
}
if len ( cfg . ID ) == 0 {
return ErrIDNotSet
}
return nil
}
func ( cfg * Config ) Merge ( override * Config ) {
if override . ClientConfig != nil {
cfg . ClientConfig = override . ClientConfig
}
if len ( override . Token ) > 0 {
cfg . Token = override . Token
}
if len ( override . ID ) > 0 {
cfg . ID = override . ID
}
if len ( override . ApiUrl ) > 0 {
cfg . ApiUrl = override . ApiUrl
}
}
// AlertProvider is the configuration necessary for sending an alert using Telegram
type AlertProvider struct {
DefaultConfig Config ` yaml:",inline" `
2022-09-08 00:50:59 +02:00
2021-10-23 22:47:12 +02:00
// DefaultAlert is the default alert configuration to use for endpoints with an alert of the appropriate type
2022-01-12 02:13:37 +01:00
DefaultAlert * alert . Alert ` yaml:"default-alert,omitempty" `
2024-07-28 01:05:17 +02:00
2024-12-17 02:32:13 +01:00
// Overrides is a list of overrides that may be prioritized over the default configuration
2024-07-28 01:05:17 +02:00
Overrides [ ] * Override ` yaml:"overrides,omitempty" `
}
// Override is a configuration that may be prioritized over the default configuration
type Override struct {
2024-12-17 02:32:13 +01:00
Group string ` yaml:"group" `
Config ` yaml:",inline" `
2021-03-31 01:38:34 +02:00
}
2024-12-17 02:32:13 +01:00
// Validate the provider's configuration
func ( provider * AlertProvider ) Validate ( ) error {
registeredGroups := make ( map [ string ] bool )
if provider . Overrides != nil {
for _ , override := range provider . Overrides {
if isAlreadyRegistered := registeredGroups [ override . Group ] ; isAlreadyRegistered || override . Group == "" {
return ErrDuplicateGroupOverride
}
registeredGroups [ override . Group ] = true
2024-07-28 01:05:17 +02:00
}
}
2024-12-17 02:32:13 +01:00
return provider . DefaultConfig . Validate ( )
2021-03-31 01:38:34 +02:00
}
2021-12-03 03:05:17 +01:00
// Send an alert using the provider
2024-05-10 04:56:16 +02:00
func ( provider * AlertProvider ) Send ( ep * endpoint . Endpoint , alert * alert . Alert , result * endpoint . Result , resolved bool ) error {
2024-12-17 02:32:13 +01:00
cfg , err := provider . GetConfig ( ep . Group , alert )
if err != nil {
return err
2022-03-05 21:44:11 +01:00
}
2024-12-17 02:32:13 +01:00
buffer := bytes . NewBuffer ( provider . buildRequestBody ( cfg , ep , alert , result , resolved ) )
request , err := http . NewRequest ( http . MethodPost , fmt . Sprintf ( "%s/bot%s/sendMessage" , cfg . ApiUrl , cfg . Token ) , buffer )
2021-12-03 03:05:17 +01:00
if err != nil {
return err
}
request . Header . Set ( "Content-Type" , "application/json" )
2024-12-17 02:32:13 +01:00
response , err := client . GetHTTPClient ( cfg . ClientConfig ) . Do ( request )
2021-12-03 03:05:17 +01:00
if err != nil {
return err
}
2022-10-20 21:16:27 +02:00
defer response . Body . Close ( )
2021-12-03 03:05:17 +01:00
if response . StatusCode > 399 {
2021-12-03 07:44:17 +01:00
body , _ := io . ReadAll ( response . Body )
2021-12-03 03:05:17 +01:00
return fmt . Errorf ( "call to provider alert returned status code %d: %s" , response . StatusCode , string ( body ) )
}
return err
}
2022-10-20 21:22:27 +02:00
type Body struct {
ChatID string ` json:"chat_id" `
Text string ` json:"text" `
ParseMode string ` json:"parse_mode" `
}
2021-12-03 03:05:17 +01:00
// buildRequestBody builds the request body for the provider
2024-12-17 02:32:13 +01:00
func ( provider * AlertProvider ) buildRequestBody ( cfg * Config , ep * endpoint . Endpoint , alert * alert . Alert , result * endpoint . Result , resolved bool ) [ ] byte {
2024-04-11 02:46:17 +02:00
var message string
2021-03-31 01:38:34 +02:00
if resolved {
2024-05-10 04:56:16 +02:00
message = fmt . Sprintf ( "An alert for *%s* has been resolved:\n—\n _healthcheck passing successfully %d time(s) in a row_\n— " , ep . DisplayName ( ) , alert . SuccessThreshold )
2021-03-31 01:38:34 +02:00
} else {
2024-05-10 04:56:16 +02:00
message = fmt . Sprintf ( "An alert for *%s* has been triggered:\n—\n _healthcheck failed %d time(s) in a row_\n— " , ep . DisplayName ( ) , alert . FailureThreshold )
2021-03-31 01:38:34 +02:00
}
2024-04-11 02:46:17 +02:00
var formattedConditionResults string
if len ( result . ConditionResults ) > 0 {
formattedConditionResults = "\n*Condition results*\n"
for _ , conditionResult := range result . ConditionResults {
var prefix string
if conditionResult . Success {
prefix = "✅"
} else {
prefix = "❌"
}
formattedConditionResults += fmt . Sprintf ( "%s - `%s`\n" , prefix , conditionResult . Condition )
2021-03-31 01:38:34 +02:00
}
}
var text string
2021-05-16 03:31:32 +02:00
if len ( alert . GetDescription ( ) ) > 0 {
2024-04-11 02:46:17 +02:00
text = fmt . Sprintf ( "⛑ *Gatus* \n%s \n*Description* \n_%s_ \n%s" , message , alert . GetDescription ( ) , formattedConditionResults )
2021-03-31 01:38:34 +02:00
} else {
2024-04-11 02:46:17 +02:00
text = fmt . Sprintf ( "⛑ *Gatus* \n%s%s" , message , formattedConditionResults )
2021-03-31 01:38:34 +02:00
}
2024-04-11 02:46:17 +02:00
bodyAsJSON , _ := json . Marshal ( Body {
2024-12-17 02:32:13 +01:00
ChatID : cfg . ID ,
2022-10-20 21:22:27 +02:00
Text : text ,
ParseMode : "MARKDOWN" ,
} )
2024-04-11 02:46:17 +02:00
return bodyAsJSON
2021-03-31 01:38:34 +02:00
}
2021-05-16 03:31:32 +02:00
2024-12-17 02:32:13 +01:00
// GetDefaultAlert returns the provider's default alert configuration
func ( provider * AlertProvider ) GetDefaultAlert ( ) * alert . Alert {
return provider . DefaultAlert
}
// GetConfig returns the configuration for the provider with the overrides applied
func ( provider * AlertProvider ) GetConfig ( group string , alert * alert . Alert ) ( * Config , error ) {
cfg := provider . DefaultConfig
// Handle group overrides
if provider . Overrides != nil {
for _ , override := range provider . Overrides {
if group == override . Group {
cfg . Merge ( & override . Config )
break
}
}
}
// Handle alert overrides
if len ( alert . ProviderOverride ) != 0 {
overrideConfig := Config { }
if err := yaml . Unmarshal ( alert . ProviderOverrideAsBytes ( ) , & overrideConfig ) ; err != nil {
return nil , err
2024-07-28 01:05:17 +02:00
}
2024-12-17 02:32:13 +01:00
cfg . Merge ( & overrideConfig )
2024-07-28 01:05:17 +02:00
}
2024-12-17 02:32:13 +01:00
// Validate the configuration
err := cfg . Validate ( )
return & cfg , err
2024-07-28 01:05:17 +02:00
}
2024-12-17 02:32:13 +01:00
// ValidateOverrides validates the alert's provider override and, if present, the group override
func ( provider * AlertProvider ) ValidateOverrides ( group string , alert * alert . Alert ) error {
_ , err := provider . GetConfig ( group , alert )
return err
2021-05-16 03:31:32 +02:00
}