From d59c1cc8d0cd4dfcc662116e5e272e0b1a8dca57 Mon Sep 17 00:00:00 2001 From: rodley82 Date: Sun, 19 Jun 2022 02:05:37 -0300 Subject: [PATCH] Extracted core logic onto slack package --- cmd/bot.go | 237 +------------------------------------ go.mod | 2 +- internal/slack/handler.go | 240 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 243 insertions(+), 236 deletions(-) create mode 100644 internal/slack/handler.go diff --git a/cmd/bot.go b/cmd/bot.go index 75ba03b..72ddfb1 100644 --- a/cmd/bot.go +++ b/cmd/bot.go @@ -1,240 +1,7 @@ package main -import ( - "context" - "errors" - "fmt" - "log" - "os" - "os/exec" - "rodleyserverbot/slack-bot/config" - "strings" - "github.com/slack-go/slack" - "github.com/slack-go/slack/slackevents" - "github.com/slack-go/slack/socketmode" -) +import "rodleyserverbot/slack-bot/internal/slack" func main() { - // Create a new client to slack by giving token - // Set debug to true while developing - // Also add a ApplicationToken option to the client - client := slack.New( - config.Config.SlackAppToken, - slack.OptionDebug(false), - slack.OptionAppLevelToken(config.Config.SlackAppToken)) - // go-slack comes with a SocketMode package that we need to use that accepts a Slack client and outputs a Socket mode client instead - socketClient := socketmode.New( - client, - socketmode.OptionDebug(false), - // Option to set a custom logger - socketmode.OptionLog(log.New(os.Stdout, "socketmode: ", log.Lshortfile|log.LstdFlags)), - ) - - // Create a context that can be used to cancel goroutine - ctx, cancel := context.WithCancel(context.Background()) - // Make this cancel called properly in a real program , graceful shutdown etc - defer cancel() - - go func(ctx context.Context, client *slack.Client, socketClient *socketmode.Client) { - // Create a for loop that selects either the context cancellation or the events incomming - for { - select { - // inscase context cancel is called exit the goroutine - case <-ctx.Done(): - log.Println("Shutting down socketmode listener") - return - case event := <-socketClient.Events: - // We have a new Events, let's type switch the event - // Add more use cases here if you want to listen to other events. - switch event.Type { - // handle EventInteractive events - case socketmode.EventTypeInteractive: - // fmt.Println("Recivimos un Interactive!", event) - - callback, ok := event.Data.(slack.InteractionCallback) - - if !ok { - log.Printf("Could not type cast the event to the MessageAction: %v\n", event) - continue - } - socketClient.Ack(*event.Request) - switch callback.Type { - case slack.InteractionTypeInteractionMessage: - //fmt.Println("type:", callback.Type, "callBackId:", callback.CallbackID, "aactions[0]", *callback.ActionCallback.AttachmentActions[0]) - action := *callback.ActionCallback.AttachmentActions[0] - //fmt.Println("Value:", action.Value) - var output string - var err error - switch action.Value { - case "poweron": - // fmt.Println("Encender!") - output, err = executeAction(action.Value) - case "poweroff": - // fmt.Println("Apagar!") - output, err = executeAction(action.Value) - } - if err == nil { - finalMessage := fmt.Sprintf("Chi chi chi chi amo! Output: %s", output) - replyToAction(callback.Channel.Name, finalMessage, client) - } - case slack.InteractionTypeMessageAction: - //fmt.Println("is a message action?") - - case slack.InteractionTypeBlockActions: - // See https://api.slack.com/apis/connections/socket-implement#button - - //fmt.Println("button clicked!") - case slack.InteractionTypeShortcut: - case slack.InteractionTypeViewSubmission: - // See https://api.slack.com/apis/connections/socket-implement#modal - case slack.InteractionTypeDialogSubmission: - default: - - } - // handle EventAPI events - case socketmode.EventTypeEventsAPI: - // The Event sent on the channel is not the same as the EventAPI events so we need to type cast it - eventsAPIEvent, ok := event.Data.(slackevents.EventsAPIEvent) - if !ok { - log.Printf("Could not type cast the event to the EventsAPIEvent: %v\n", event) - continue - } - // We need to send an Acknowledge to the slack server - socketClient.Ack(*event.Request) - // Now we have an Events API event, but this event type can in turn be many types, so we actually need another type switch - // log.Println(eventsAPIEvent) - err := handleEventMessage(eventsAPIEvent, client) - if err != nil { - // Replace with actual err handeling - log.Fatal(err) - } - } - - } - } - }(ctx, client, socketClient) - - socketClient.Run() -} - -// handleEventMessage will take an event and handle it properly based on the type of event -func handleEventMessage(event slackevents.EventsAPIEvent, client *slack.Client) error { - switch event.Type { - // First we check if this is an CallbackEvent - case slackevents.CallbackEvent: - - innerEvent := event.InnerEvent - // fmt.Println("We received an event!", innerEvent) - // Yet Another Type switch on the actual Data to see if its an AppMentionEvent - switch ev := innerEvent.Data.(type) { - case *slackevents.AppMentionEvent: - // The application has been mentioned since this Event is a Mention event - log.Println(ev) - handleAppMentionEvent(ev, client) - } - default: - return errors.New("unsupported event type") - } - return nil -} - -func executeAction(actionName string) (string, error) { - var output_str string - switch actionName { - case "poweron": - cmd := exec.Command("/bin/bash", os.Getenv("POWERON_SCRIPT_PATH")) - output, err := cmd.Output() - - if err != nil { - fmt.Printf("error %s", err) - return "", err - } - output_str = string(output) - //fmt.Println("output:", output_str) - // return output - case "poweroff": - cmd := exec.Command("/bin/bash", os.Getenv("POWEROFF_SCRIPT_PATH")) - output, err := cmd.Output() - - if err != nil { - fmt.Printf("error %s", err) - return "", err - } - output_str = string(output) - //fmt.Println("output:", output_str) - } - - return output_str, nil -} - -func replyToAction(channelName string, message string, client *slack.Client) error { - attachment := slack.Attachment{} - attachment.Text = message - attachment.Color = "#4af030" - _, _, err := client.PostMessage(channelName, slack.MsgOptionAttachments(attachment)) - if err != nil { - return fmt.Errorf("failed to post message: %w", err) - } - return nil -} - -// handleAppMentionEvent is used to take care of the AppMentionEvent when the bot is mentioned -func handleAppMentionEvent(event *slackevents.AppMentionEvent, client *slack.Client) error { - - // Grab the user name based on the ID of the one who mentioned the bot - user, err := client.GetUserInfo(event.User) - if err != nil { - return err - } - // Check if the user said Hello to the bot - text := strings.ToLower(event.Text) - - // Create the attachment and assigned based on the message - attachment := slack.Attachment{} - // Add Some default context like user who mentioned the bot - // attachment.Fields = []slack.AttachmentField{ - // { - // Title: "Date", - // Value: time.Now().String(), - // }, { - // Title: "Initializer", - // Value: user.Name, - // }, - // } - attachment.CallbackID = "server_action" - if strings.Contains(text, "hello") { - // Greet the user - attachment.Text = fmt.Sprintf("Hello %s", user.Name) - attachment.Pretext = "Greetings" - attachment.Color = "#4af030" - } else { - // Send a message to the user - attachment.Text = "Por el momento esto es lo que se hacer" - attachment.Pretext = fmt.Sprintf("Como te puedo ayudar %s?", user.Name) - attachment.Color = "#3d3d3d" - powerOnAction := slack.AttachmentAction{} - powerOnAction.Name = "poweron" - powerOnAction.Text = "Encender" - powerOnAction.Value = "poweron" - powerOnAction.Type = "button" - - powerOffAction := slack.AttachmentAction{} - powerOffAction.Name = "poweroff" - powerOffAction.Text = "Apagar" - powerOffAction.Value = "poweroff" - powerOffAction.Type = "button" - powerOffAction.Style = "danger" - - actions := []slack.AttachmentAction{powerOnAction, powerOffAction} - //fmt.Println(actions) - attachment.Actions = actions - - } - // Send the message to the channel - // The Channel is available in the event message - _, _, err = client.PostMessage(event.Channel, slack.MsgOptionAttachments(attachment)) - if err != nil { - return fmt.Errorf("failed to post message: %w", err) - } - return nil + slack.Start() } diff --git a/go.mod b/go.mod index 42f5951..3c194ee 100644 --- a/go.mod +++ b/go.mod @@ -5,5 +5,5 @@ go 1.13 require ( github.com/gorilla/websocket v1.5.0 // indirect github.com/joho/godotenv v1.4.0 - github.com/slack-go/slack v0.11.0 // indirect + github.com/slack-go/slack v0.11.0 ) diff --git a/internal/slack/handler.go b/internal/slack/handler.go new file mode 100644 index 0000000..66d7a44 --- /dev/null +++ b/internal/slack/handler.go @@ -0,0 +1,240 @@ +package slack + +import ( + "context" + "errors" + "fmt" + "log" + "os" + "os/exec" + "rodleyserverbot/slack-bot/config" + "strings" + "github.com/slack-go/slack" + "github.com/slack-go/slack/slackevents" + "github.com/slack-go/slack/socketmode" +) + +func Start() { + // Create a new client to slack by giving token + // Set debug to true while developing + // Also add a ApplicationToken option to the client + client := slack.New( + config.Config.SlackAppToken, + slack.OptionDebug(false), + slack.OptionAppLevelToken(config.Config.SlackAppToken)) + // go-slack comes with a SocketMode package that we need to use that accepts a Slack client and outputs a Socket mode client instead + socketClient := socketmode.New( + client, + socketmode.OptionDebug(false), + // Option to set a custom logger + socketmode.OptionLog(log.New(os.Stdout, "socketmode: ", log.Lshortfile|log.LstdFlags)), + ) + + // Create a context that can be used to cancel goroutine + ctx, cancel := context.WithCancel(context.Background()) + // Make this cancel called properly in a real program , graceful shutdown etc + defer cancel() + + go func(ctx context.Context, client *slack.Client, socketClient *socketmode.Client) { + // Create a for loop that selects either the context cancellation or the events incomming + for { + select { + // inscase context cancel is called exit the goroutine + case <-ctx.Done(): + log.Println("Shutting down socketmode listener") + return + case event := <-socketClient.Events: + // We have a new Events, let's type switch the event + // Add more use cases here if you want to listen to other events. + switch event.Type { + // handle EventInteractive events + case socketmode.EventTypeInteractive: + // fmt.Println("Recivimos un Interactive!", event) + + callback, ok := event.Data.(slack.InteractionCallback) + + if !ok { + log.Printf("Could not type cast the event to the MessageAction: %v\n", event) + continue + } + socketClient.Ack(*event.Request) + switch callback.Type { + case slack.InteractionTypeInteractionMessage: + //fmt.Println("type:", callback.Type, "callBackId:", callback.CallbackID, "aactions[0]", *callback.ActionCallback.AttachmentActions[0]) + action := *callback.ActionCallback.AttachmentActions[0] + //fmt.Println("Value:", action.Value) + var output string + var err error + switch action.Value { + case "poweron": + // fmt.Println("Encender!") + output, err = executeAction(action.Value) + case "poweroff": + // fmt.Println("Apagar!") + output, err = executeAction(action.Value) + } + if err == nil { + finalMessage := fmt.Sprintf("Chi chi chi chi amo! Output: %s", output) + replyToAction(callback.Channel.Name, finalMessage, client) + } + case slack.InteractionTypeMessageAction: + //fmt.Println("is a message action?") + + case slack.InteractionTypeBlockActions: + // See https://api.slack.com/apis/connections/socket-implement#button + + //fmt.Println("button clicked!") + case slack.InteractionTypeShortcut: + case slack.InteractionTypeViewSubmission: + // See https://api.slack.com/apis/connections/socket-implement#modal + case slack.InteractionTypeDialogSubmission: + default: + + } + // handle EventAPI events + case socketmode.EventTypeEventsAPI: + // The Event sent on the channel is not the same as the EventAPI events so we need to type cast it + eventsAPIEvent, ok := event.Data.(slackevents.EventsAPIEvent) + if !ok { + log.Printf("Could not type cast the event to the EventsAPIEvent: %v\n", event) + continue + } + // We need to send an Acknowledge to the slack server + socketClient.Ack(*event.Request) + // Now we have an Events API event, but this event type can in turn be many types, so we actually need another type switch + // log.Println(eventsAPIEvent) + err := handleEventMessage(eventsAPIEvent, client) + if err != nil { + // Replace with actual err handeling + log.Fatal(err) + } + } + + } + } + }(ctx, client, socketClient) + + socketClient.Run() +} + +// handleEventMessage will take an event and handle it properly based on the type of event +func handleEventMessage(event slackevents.EventsAPIEvent, client *slack.Client) error { + switch event.Type { + // First we check if this is an CallbackEvent + case slackevents.CallbackEvent: + + innerEvent := event.InnerEvent + // fmt.Println("We received an event!", innerEvent) + // Yet Another Type switch on the actual Data to see if its an AppMentionEvent + switch ev := innerEvent.Data.(type) { + case *slackevents.AppMentionEvent: + // The application has been mentioned since this Event is a Mention event + log.Println(ev) + handleAppMentionEvent(ev, client) + } + default: + return errors.New("unsupported event type") + } + return nil +} + +func executeAction(actionName string) (string, error) { + var output_str string + switch actionName { + case "poweron": + cmd := exec.Command("/bin/bash", os.Getenv("POWERON_SCRIPT_PATH")) + output, err := cmd.Output() + + if err != nil { + fmt.Printf("error %s", err) + return "", err + } + output_str = string(output) + //fmt.Println("output:", output_str) + // return output + case "poweroff": + cmd := exec.Command("/bin/bash", os.Getenv("POWEROFF_SCRIPT_PATH")) + output, err := cmd.Output() + + if err != nil { + fmt.Printf("error %s", err) + return "", err + } + output_str = string(output) + //fmt.Println("output:", output_str) + } + + return output_str, nil +} + +func replyToAction(channelName string, message string, client *slack.Client) error { + attachment := slack.Attachment{} + attachment.Text = message + attachment.Color = "#4af030" + _, _, err := client.PostMessage(channelName, slack.MsgOptionAttachments(attachment)) + if err != nil { + return fmt.Errorf("failed to post message: %w", err) + } + return nil +} + +// handleAppMentionEvent is used to take care of the AppMentionEvent when the bot is mentioned +func handleAppMentionEvent(event *slackevents.AppMentionEvent, client *slack.Client) error { + + // Grab the user name based on the ID of the one who mentioned the bot + user, err := client.GetUserInfo(event.User) + if err != nil { + return err + } + // Check if the user said Hello to the bot + text := strings.ToLower(event.Text) + + // Create the attachment and assigned based on the message + attachment := slack.Attachment{} + // Add Some default context like user who mentioned the bot + // attachment.Fields = []slack.AttachmentField{ + // { + // Title: "Date", + // Value: time.Now().String(), + // }, { + // Title: "Initializer", + // Value: user.Name, + // }, + // } + attachment.CallbackID = "server_action" + if strings.Contains(text, "hello") { + // Greet the user + attachment.Text = fmt.Sprintf("Hello %s", user.Name) + attachment.Pretext = "Greetings" + attachment.Color = "#4af030" + } else { + // Send a message to the user + attachment.Text = "Por el momento esto es lo que se hacer" + attachment.Pretext = fmt.Sprintf("Como te puedo ayudar %s?", user.Name) + attachment.Color = "#3d3d3d" + powerOnAction := slack.AttachmentAction{} + powerOnAction.Name = "poweron" + powerOnAction.Text = "Encender" + powerOnAction.Value = "poweron" + powerOnAction.Type = "button" + + powerOffAction := slack.AttachmentAction{} + powerOffAction.Name = "poweroff" + powerOffAction.Text = "Apagar" + powerOffAction.Value = "poweroff" + powerOffAction.Type = "button" + powerOffAction.Style = "danger" + + actions := []slack.AttachmentAction{powerOnAction, powerOffAction} + //fmt.Println(actions) + attachment.Actions = actions + + } + // Send the message to the channel + // The Channel is available in the event message + _, _, err = client.PostMessage(event.Channel, slack.MsgOptionAttachments(attachment)) + if err != nil { + return fmt.Errorf("failed to post message: %w", err) + } + return nil +}