237 lines
5.1 KiB
Go
237 lines
5.1 KiB
Go
package bot
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/nlopes/slack"
|
|
|
|
"github.com/rusenask/keel/provider/kubernetes"
|
|
|
|
log "github.com/Sirupsen/logrus"
|
|
)
|
|
|
|
var (
|
|
botEventTextToResponse = map[string][]string{
|
|
"help": {
|
|
`Here's a list of supported commands`,
|
|
`- "get deployments" -> get a list of all deployments`,
|
|
// `- "get deployments all" -> get a list of all deployments`,
|
|
// `- "describe deployment <deployment>" -> get details for specified deployment`,
|
|
},
|
|
}
|
|
|
|
// static bot commands can be used straight away
|
|
staticBotCommands = map[string]bool{
|
|
"get deployments": true,
|
|
"get deployments all": true,
|
|
}
|
|
|
|
// dynamic bot command prefixes have to be matched
|
|
dynamicBotCommandPrefixes = []string{"describe deployment"}
|
|
)
|
|
|
|
type Bot struct {
|
|
id string // bot id
|
|
name string // bot name
|
|
|
|
users map[string]string
|
|
|
|
msgPrefix string
|
|
|
|
slackClient *slack.Client
|
|
slackRTM *slack.RTM
|
|
|
|
k8sImplementer kubernetes.Implementer
|
|
|
|
ctx context.Context
|
|
}
|
|
|
|
func New(name, token string, k8sImplementer kubernetes.Implementer) *Bot {
|
|
client := slack.New(token)
|
|
|
|
return &Bot{
|
|
slackClient: client,
|
|
k8sImplementer: k8sImplementer,
|
|
name: name,
|
|
}
|
|
}
|
|
|
|
// Start - start bot
|
|
func (b *Bot) Start(ctx context.Context) error {
|
|
|
|
// setting root context
|
|
b.ctx = ctx
|
|
|
|
users, err := b.slackClient.GetUsers()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
b.users = map[string]string{}
|
|
|
|
for _, user := range users {
|
|
switch user.Name {
|
|
case b.name:
|
|
if user.IsBot {
|
|
b.id = user.ID
|
|
}
|
|
default:
|
|
continue
|
|
}
|
|
}
|
|
if b.id == "" {
|
|
return errors.New("could not find bot in the list of names, check if the bot is called \"" + b.name + "\" ")
|
|
}
|
|
|
|
b.msgPrefix = strings.ToLower("<@" + b.id + ">")
|
|
|
|
go b.startInternal()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (b *Bot) startInternal() error {
|
|
b.slackRTM = b.slackClient.NewRTM()
|
|
|
|
go b.slackRTM.ManageConnection()
|
|
|
|
for {
|
|
select {
|
|
case <-b.ctx.Done():
|
|
return nil
|
|
|
|
case msg := <-b.slackRTM.IncomingEvents:
|
|
switch ev := msg.Data.(type) {
|
|
case *slack.HelloEvent:
|
|
// Ignore hello
|
|
|
|
case *slack.ConnectedEvent:
|
|
// fmt.Println("Infos:", ev.Info)
|
|
// fmt.Println("Connection counter:", ev.ConnectionCount)
|
|
// Replace #general with your Channel ID
|
|
// b.slackRTM.SendMessage(b.slackRTM.NewOutgoingMessage("Hello world", "#general"))
|
|
|
|
case *slack.MessageEvent:
|
|
b.handleMessage(ev)
|
|
case *slack.PresenceChangeEvent:
|
|
// fmt.Printf("Presence Change: %v\n", ev)
|
|
|
|
// case *slack.LatencyReport:
|
|
// fmt.Printf("Current latency: %v\n", ev.Value)
|
|
|
|
case *slack.RTMError:
|
|
fmt.Printf("Error: %s\n", ev.Error())
|
|
|
|
case *slack.InvalidAuthEvent:
|
|
fmt.Printf("Invalid credentials")
|
|
return fmt.Errorf("invalid credentials")
|
|
|
|
default:
|
|
|
|
// Ignore other events..
|
|
// fmt.Printf("Unexpected: %v\n", msg.Data)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (b *Bot) handleMessage(event *slack.MessageEvent) {
|
|
if event.BotID != "" || event.User == "" || event.SubType == "bot_message" {
|
|
log.WithFields(log.Fields{
|
|
"event_bot_ID": event.BotID,
|
|
"event_user": event.User,
|
|
"event_subtype": event.SubType,
|
|
}).Info("handleMessage: ignoring message")
|
|
return
|
|
}
|
|
|
|
eventText := strings.Trim(strings.ToLower(event.Text), " \n\r")
|
|
|
|
// All messages past this point are directed to @gopher itself
|
|
if !b.isBotMessage(event, eventText) {
|
|
return
|
|
}
|
|
|
|
eventText = b.trimBot(eventText)
|
|
|
|
// Responses that are just a canned string response
|
|
if responseLines, ok := botEventTextToResponse[eventText]; ok {
|
|
response := strings.Join(responseLines, "\n")
|
|
b.respond(event, formatAsSnippet(response))
|
|
return
|
|
}
|
|
|
|
if b.isCommand(event, eventText) {
|
|
b.handleCommand(event, eventText)
|
|
return
|
|
}
|
|
|
|
log.WithFields(log.Fields{
|
|
"name": b.name,
|
|
"bot_id": b.id,
|
|
"command": eventText,
|
|
"untrimmed": strings.Trim(strings.ToLower(event.Text), " \n\r"),
|
|
}).Debug("handleMessage: bot couldn't recognise command")
|
|
}
|
|
|
|
func (b *Bot) isCommand(event *slack.MessageEvent, eventText string) bool {
|
|
if staticBotCommands[eventText] {
|
|
return true
|
|
}
|
|
|
|
for _, prefix := range dynamicBotCommandPrefixes {
|
|
if strings.HasPrefix(eventText, prefix) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (b *Bot) handleCommand(event *slack.MessageEvent, eventText string) {
|
|
switch eventText {
|
|
case "get deployments":
|
|
log.Info("getting deployments")
|
|
response := b.deploymentsResponse(Filter{})
|
|
b.respond(event, formatAsSnippet(response))
|
|
return
|
|
}
|
|
|
|
log.Info("command not found")
|
|
}
|
|
|
|
func (b *Bot) respond(event *slack.MessageEvent, response string) {
|
|
b.slackRTM.SendMessage(b.slackRTM.NewOutgoingMessage(response, event.Channel))
|
|
}
|
|
|
|
func (b *Bot) isBotMessage(event *slack.MessageEvent, eventText string) bool {
|
|
prefixes := []string{
|
|
b.msgPrefix,
|
|
"keel",
|
|
}
|
|
|
|
for _, p := range prefixes {
|
|
if strings.HasPrefix(eventText, p) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
// Direct message channels always starts with 'D'
|
|
return strings.HasPrefix(event.Channel, "D")
|
|
}
|
|
|
|
func (b *Bot) trimBot(msg string) string {
|
|
msg = strings.Replace(msg, strings.ToLower(b.msgPrefix), "", 1)
|
|
msg = strings.TrimPrefix(msg, b.name)
|
|
msg = strings.Trim(msg, " :\n")
|
|
|
|
return msg
|
|
}
|
|
|
|
func formatAsSnippet(response string) string {
|
|
return "```" + response + "```"
|
|
}
|