diff --git a/.gitignore b/.gitignore index 66fd13c..f0c6af6 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,7 @@ # Dependency directories (remove the comment below to include it) # vendor/ +go.mod +go.sum + +config.json \ No newline at end of file diff --git a/env/env.go b/env/env.go new file mode 100644 index 0000000..9cd0ed5 --- /dev/null +++ b/env/env.go @@ -0,0 +1,55 @@ +package env + +import ( + "encoding/json" + "io/ioutil" + "log" + "os" +) + +var ( + configuration *Static + ConfigFlag string = "DOWNDETECTOR_CONFIG" +) + +type Static struct { + BotToken string `json:"bot_token"` + BotGuild string `json:"bot_guild"` + Address string `json:"address"` + ChannelName string `json:"channelName"` + Checks []Check +} + +type Check struct { + Type string `json:"type"` + Value string `json:"value"` + Interval string `json:"interval"` + Parameters *Parameters `json:"parameters"` +} + +type Parameters struct { + StatusCode int `json:"status_code"` +} + +func Configuration() *Static { + if configuration == nil { + var path string + if path = os.Getenv(ConfigFlag); path == "" { + path = "./config.json" + } + + file, err := ioutil.ReadFile(path) + if err != nil { + log.Printf("[ERROR] %s\n", err.Error()) + return nil + } + var s Static + if err := json.Unmarshal(file, &s); err != nil { + log.Printf("[ERROR] unmarshal file: %s", err.Error()) + return nil + } + configuration = &s + + } + return configuration +} diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..619619b --- /dev/null +++ b/go.sum @@ -0,0 +1,16 @@ +github.com/Clinet/discordgo-embed v0.0.0-20220113222025-bafe0c917646 h1:KkKIDMzyOhNnW5ew6KpRvEflciWqo09NmgVGqTZEj3M= +github.com/Clinet/discordgo-embed v0.0.0-20220113222025-bafe0c917646/go.mod h1:0ydUl+01209LCyzJk68BeRtCN1IMrNJgX4IBmwmC1f8= +github.com/bwmarrin/discordgo v0.25.0 h1:NXhdfHRNxtwso6FPdzW2i3uBvvU7UIQTghmV2T4nqAs= +github.com/bwmarrin/discordgo v0.25.0/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/infiniteloopcloud/discord-jira v0.2.0 h1:w3PuND+cojzqfbkk1UinZHSU3c86giqwSF9idFbDBJY= +github.com/infiniteloopcloud/discord-jira v0.2.0/go.mod h1:G3sQnTp4hX9/rcl0qc0dhRSPlJU/R9lbk/xOqc8dy10= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/handler/handler.go b/handler/handler.go new file mode 100644 index 0000000..df96a48 --- /dev/null +++ b/handler/handler.go @@ -0,0 +1,56 @@ +package handler + +import ( + "log" + "net/http" + "strconv" + + embed "github.com/Clinet/discordgo-embed" + "github.com/bwmarrin/discordgo" + env "github.com/infiniteloopcloud/discord-downdetector/env" +) + +const ( + warning = 0xD10000 +) + +var channelName string + +// Check is the endpoint alive +func Handle(body env.Check) (string, *discordgo.MessageEmbed, error) { + var statusCode = http.StatusOK + code := checkHealth(body) + + if body.Parameters != nil && body.Parameters.StatusCode != 0 { + statusCode = body.Parameters.StatusCode + } + if code != statusCode { + return unreachable(body, code) + } else { + return "", nil, nil + } + +} + +// Send an embed to the downdetector channel +func unreachable(check env.Check, code int) (string, *discordgo.MessageEmbed, error) { + status := strconv.Itoa(code) + message := embed.NewEmbed(). + SetAuthor("Status code: " + status). + SetTitle("[Host unreachable] " + check.Value). + SetColor(warning) + + return env.Configuration().ChannelName, message.MessageEmbed, nil +} + +// Return the status code of the request +func checkHealth(check env.Check) int { + resp, err := http.Get(check.Type + "://" + check.Value) + if err != nil { + log.Println("[ERROR]", err) + return resp.StatusCode + } + defer resp.Body.Close() + + return resp.StatusCode +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..a2fcd9a --- /dev/null +++ b/main.go @@ -0,0 +1,9 @@ +package main + +import ( + runner "github.com/infiniteloopcloud/discord-downdetector/runner" +) + +func main() { + runner.Run() +} diff --git a/runner/runner.go b/runner/runner.go new file mode 100644 index 0000000..9cdebc9 --- /dev/null +++ b/runner/runner.go @@ -0,0 +1,51 @@ +package runner + +import ( + "log" + "time" + + "github.com/infiniteloopcloud/discord-downdetector/env" + handler "github.com/infiniteloopcloud/discord-downdetector/handler" + utils "github.com/infiniteloopcloud/discord-downdetector/utils" +) + +func check(body env.Check) { + channel, message, err := handler.Handle(body) + if err != nil { + log.Printf("[ERROR] %s", err.Error()) + return + } + channelID := utils.GetChannelID(channel) + if channelID == "" { + channelID = utils.GetChannelID("unknown") + } + if channelID != "" && message != nil { + _, err = utils.GetSession().ChannelMessageSendEmbed(channelID, message) + if err != nil { + log.Printf("[ERROR] %s", err.Error()) + } + } +} + +func Run() { + log.Printf("[RUNNING] Downdetector") + + // A loop what runs forever to check is the host reachable + for { + for i := range env.Configuration().Checks { + check(env.Configuration().Checks[i]) + } + // Checks only the first object's interval + // Don't wait between objects + interval, unit := utils.GetTime(env.Configuration().Checks[0].Interval) + switch unit { + case "h": + time.Sleep(time.Duration(interval) * time.Hour) + case "m": + time.Sleep(time.Duration(interval) * time.Minute) + case "s": + time.Sleep(time.Duration(interval) * time.Second) + } + } + +} diff --git a/utils/utils.go b/utils/utils.go new file mode 100644 index 0000000..868aa57 --- /dev/null +++ b/utils/utils.go @@ -0,0 +1,61 @@ +package utils + +import ( + "encoding/json" + "log" + "strconv" + + "github.com/bwmarrin/discordgo" + "github.com/infiniteloopcloud/discord-downdetector/env" +) + +var session *discordgo.Session +var channelsCache map[string]string + +func GetTime(x string) (int, string) { + i := x[len(x)-1:] + unitVal := x[:len(x)-1] + unit, _ := strconv.Atoi(unitVal) + return unit, i +} + +func GetEvent(raw []byte) (string, error) { + var static env.Static + err := json.Unmarshal(raw, &static) + if err != nil { + return "", err + } + return env.Configuration().ChannelName, nil +} + +func GetChannelID(name string) string { + if channelsCache == nil { + channelsCache = make(map[string]string) + } + if id, ok := channelsCache[name]; ok { + return id + } else { + channels, err := GetSession().GuildChannels(env.Configuration().BotGuild) + if err != nil { + log.Print(err) + } + for _, channel := range channels { + if name == channel.Name { + channelsCache[channel.Name] = channel.ID + return channel.ID + } + } + } + return "" +} + +func GetSession() *discordgo.Session { + if session == nil { + var err error + session, err = discordgo.New("Bot " + env.Configuration().BotToken) + if err != nil { + log.Printf("[ERROR] %s", err.Error()) + } + } + return session +}