package slack import ( "context" "errors" "fmt" "log" "os" "time" "os/exec" "rodleyserverbot/slack-bot/config" "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.SlackAuthToken, 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: 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: action := *callback.ActionCallback.AttachmentActions[0] var output string var err error replyToAction(callback.Channel.ID, "Chi Chi Chi Chi Amo, trabajando, tenga paciencia :pray:", client) output, err = executeAction(action.Value) if err == nil { finalMessage := fmt.Sprintf("Resultado*\n```\n%s\n```", output) replyToAction(callback.Channel.ID, finalMessage, client) handleAppMessagedEvent(nil, callback.Channel.ID, client) } case slack.InteractionTypeMessageAction: case slack.InteractionTypeBlockActions: // See https://api.slack.com/apis/connections/socket-implement#button 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:", eventsAPIEvent) err := handleEventMessage(eventsAPIEvent, client) if err != nil { // Replace with actual err handeling // log.Fatal(err) } default: } } } }(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 // 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("App Mentioned!", ev) handleAppMentionEvent(ev, client) case *slackevents.MessageEvent: //log.Println("MessageEvent!", ev) handleAppMessagedEvent(ev, ev.Channel, client) default: } default: return errors.New("unsupported event type") } return nil } func executeAction(actionName string) (string, error) { var output_str string var err error for _, action := range config.Config.Actions { if action.Name == actionName { output_str, err = executeScript(action.Path) if err != nil { fmt.Printf("error %s", err) return "", err } } } return output_str, err } func executeScript(scriptPath string) (string, error) { // Create a new context and add a timeout to it ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() // The cancel should be deferred so resources are cleaned up // Create the command with our context cmd := exec.CommandContext(ctx, "/bin/bash", scriptPath) // This time we can simply use Output() to get the result. out, err := cmd.Output() // We want to check the context error to see if the timeout was executed. // The error returned by cmd.Output() will be OS specific based on what // happens when a process is killed. if ctx.Err() == context.DeadlineExceeded { log.Println("Command timed out") return "", context.DeadlineExceeded } // If there's no context error, we know the command completed (or errored). if err != nil { log.Println("Non-zero exit code:", err) } output_str := string(out) 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 { log.Println("failed to post message:", err) return err } return nil } func getAttachmentButtons() []slack.AttachmentAction{ var actions []slack.AttachmentAction for _, action := range config.Config.Actions { slackAction := slack.AttachmentAction{} slackAction.Name = action.Name slackAction.Value = action.Name slackAction.Text = action.DisplayName slackAction.Type = "button" actions=append(actions, slackAction) } return actions } func handleAppMessagedEvent(event *slackevents.MessageEvent, channel string,client *slack.Client) error { if event != nil && event.BotID != "" { // We're not interested in messages from ourselves or other bots return nil } attachment := slack.Attachment{} var err error attachment.CallbackID = "server_action" // 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?") attachment.Color = "#3d3d3d" actions := getAttachmentButtons() attachment.Actions = actions // Send the message to the channel // The Channel is available in the event message _, _, err = client.PostMessage(channel, 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 { var err error // Create the attachment and assigned based on the message attachment := slack.Attachment{} attachment.CallbackID = "server_action" // 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?") attachment.Color = "#3d3d3d" actions := getAttachmentButtons() 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 }