You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

264 lines
8.9 KiB

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:
//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
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)
}
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:
//fmt.Println("Don't know what type it is")
}
// 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:
//fmt.Println("Event received no matching TYPE:", event.Type, "event:", event)
}
}
}
}(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("App Mentioned!", ev)
handleAppMentionEvent(ev, client)
case *slackevents.MessageEvent:
//log.Println("MessageEvent!", ev)
handleAppMessagedEvent(ev, client)
default:
//fmt.Println("Not handled EventMessage!:", ev)
}
default:
return errors.New("unsupported event type")
}
return nil
}
func executeAction(actionName string) (string, error) {
//fmt.Println("executeAction actionName:", actionName, "actions:", config.Config.Actions)
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 {
fmt.Println("Command timed out")
return "", context.DeadlineExceeded
}
// If there's no context error, we know the command completed (or errored).
if err != nil {
fmt.Println("Non-zero exit code:", err)
}
output_str := string(out)
// fmt.Println("Output:", output_str)
return output_str, nil
}
func replyToAction(channelName string, message string, client *slack.Client) error {
//fmt.Println("replyToAction channel:", channelName, "Message:", message)
attachment := slack.Attachment{}
attachment.Text = message
attachment.Color = "#4af030"
_, _, err := client.PostMessage(channelName, slack.MsgOptionAttachments(attachment))
if err != nil {
//fmt.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, client *slack.Client) error {
//fmt.Println("handleAppMessagedEvent event:", event, "BotID:", event.BotID)
if 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(event.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 {
// fmt.Println("handleAppMentionEvent event:", event)
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()
//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
}