From c38ee89a69f164684f957e71c2697814a13ee6b9 Mon Sep 17 00:00:00 2001 From: rodley82 Date: Fri, 17 Jun 2022 01:18:42 -0300 Subject: [PATCH] initial working version based on a medium article --- .env.example | 3 + README.md | 0 go.mod | 9 +++ go.sum | 14 +++++ main.go | 168 +++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 194 insertions(+) create mode 100644 .env.example create mode 100644 README.md create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..0b181ce --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +SLACK_AUTH_TOKEN="" +SLACK_CHANNEL_ID="" +SLACK_APP_TOKEN="" diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..af6a051 --- /dev/null +++ b/go.mod @@ -0,0 +1,9 @@ +module rodleyserverbot/slack-bot + +go 1.13 + +require ( + github.com/gorilla/websocket v1.5.0 // indirect + github.com/joho/godotenv v1.4.0 // indirect + github.com/slack-go/slack v0.11.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..aac87c5 --- /dev/null +++ b/go.sum @@ -0,0 +1,14 @@ +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +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/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= +github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/slack-go/slack v0.11.0 h1:sBBjQz8LY++6eeWhGJNZpRm5jvLRNnWBFZ/cAq58a6k= +github.com/slack-go/slack v0.11.0/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/main.go b/main.go new file mode 100644 index 0000000..5549d15 --- /dev/null +++ b/main.go @@ -0,0 +1,168 @@ +package main + +import ( + "context" + "errors" + "fmt" + "log" + "os" + "strings" + + "github.com/joho/godotenv" + "github.com/slack-go/slack" + "github.com/slack-go/slack/slackevents" + "github.com/slack-go/slack/socketmode" +) + +func main() { + + // Load Env variables from .dot file + godotenv.Load(".env") + + token := os.Getenv("SLACK_AUTH_TOKEN") + appToken := os.Getenv("SLACK_APP_TOKEN") + // 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(token, slack.OptionDebug(true), slack.OptionAppLevelToken(appToken)) + // 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(true), + // 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) + eventsInteractive, ok := event.Data.(slackevents.EventsAPICallbackEvent) + if !ok { + log.Printf("Could not type cast the event to the EventsAPICallbackEvent: %v\n", event) + continue + } + fmt.Println("Se pudo castear!!", eventsInteractive) + // 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 +} + +// 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 +}