slack-go / slack

Slack API in Go, originally by @nlopes; Maintainers needed, contact @parsley42
https://pkg.go.dev/github.com/slack-go/slack
BSD 2-Clause "Simplified" License
4.6k stars 1.11k forks source link

Slash Command returns "bad_error_message" #1267

Closed talaniz closed 4 months ago

talaniz commented 4 months ago

What happened

I'm following this tutorial for creating a Slack bot in socket mode. I've created a very basic "hello" command and reinstalled the app. However, when I run the code and attempt the command "/hello reader" per the tutorial, I get "/hello failed with the error "dispatch_failed"" instead of processing the command. I tried adding a default case to the switch statement and it returns ****** Received Event: {error_bad_message 0x1400029e5d0 <nil>}, which appears to come from the slack-go library.

Sample websocket message

socketmode: 2024/02/21 15:20:38 socket_mode_managed_conn.go:378: Received WebSocket message: {"envelope_id":"276bcbd8-cdba-48f6-95f6-6680f75728b4","payload":
{"token":"TOKEN","team_id":"T015M2ZDW58","team_domain":"DOMAIN","channel_id":"C015T89B4AY","channel_name":"general","user_id":"U015ZMBAL1X"
,"user_name":"USER.NAME","command":"\/hello","text":"reader","api_app_id":"A015T8ETJ92",
"is_enterprise_install":"false","response_url":"https:\/\/hooks.slack.com\/commands\/T015M2ZDW58\/6681237517090\/hY68y9aB79puxzLfT1id5c5f","trigger_id":"6704023023520.1191101472178.1810964e79c5559c22273228f710f5f7"},
"type":"slash_commands","accepts_response_payload":true}

Expected behavior

It looks like the code should process the command correctly.

Steps to reproduce

Follow the tutorial to create the slash command (code below).

reproducible code

package main

import (
    "context"
    "errors"
    "fmt"
    "log"
    "os"
    "strings"
    "time"

    "github.com/joho/godotenv"
    "github.com/slack-go/slack"
    "github.com/slack-go/slack/slackevents"
    "github.com/slack-go/slack/socketmode"
)

func handleAppMentionEvent(event *slackevents.AppMentionEvent, client *slack.Client) error {

    user, err := client.GetUserInfo(event.User)
    if err != nil {
        return err
    }
    text := strings.ToLower(event.Text)

    attachment := slack.Attachment{}
    attachment.Fields = []slack.AttachmentField{
        {
            Title: "Date",
            Value: time.Now().String(),
        }, {
            Title: "Initializer",
            Value: user.Name,
        },
    }
    if strings.Contains(text, "hello") {
        attachment.Text = fmt.Sprintf("Hello %s", user.Name)
        attachment.Pretext = "Greetings"
        attachment.Color = "#4af030"
    } else {
        attachment.Text = fmt.Sprintf("How can I help you %s?", user.Name)
        attachment.Pretext = "How can I be of service"
        attachment.Color = "#3d3d3d"
    }
    _, _, err = client.PostMessage(event.Channel, slack.MsgOptionAttachments(attachment))
    if err != nil {
        return fmt.Errorf("failed to post message: %w", err)
    }
    return nil
}

func handleHelloCommand(command slack.SlashCommand, client *slack.Client) error {
    attachment := slack.Attachment{}
    attachment.Fields = []slack.AttachmentField{
        {
            Title: "Date",
            Value: time.Now().String(),
        }, {
            Title: "Initializer",
            Value: command.UserName,
        },
    }
    attachment.Text = fmt.Sprintf("Hello %s", command.Text)
    attachment.Color = "#4af030"

    _, _, err := client.PostMessage(command.ChannelID, slack.MsgOptionAttachments(attachment))
    if err != nil {
        return fmt.Errorf("failed to post message: %w", err)
    }
    return nil
}

// handleSlashCommand handles slash commands by routing them to the appropriate function
func handleSlashCommand(command slack.SlashCommand, client *slack.Client) error {
    log.Println("Received slash command", command)
    switch command.Command {
    case "/hello":
        return handleHelloCommand(command, client)
    }
    return nil
}

// handleEventMessage will take an event and handle itproperly based on the event type
func handleEventMessage(event slackevents.EventsAPIEvent, client *slack.Client) error {
    switch event.Type {

    case slackevents.CallbackEvent:
        innerEvent := event.InnerEvent
        switch ev := innerEvent.Data.(type) {
        case *slackevents.AppMentionEvent:
            err := handleAppMentionEvent(ev, client)
            if err != nil {
                return err
            }
        }
    default:
        return errors.New("unsupported event type")
    }
    return nil
}

func main() {
    godotenv.Load(".env")

    token := os.Getenv("SLACK_BOT_TOKEN")
    appToken := os.Getenv("SLACK_APP_TOKEN")

    client := slack.New(token, slack.OptionDebug(true), slack.OptionAppLevelToken(appToken))
    sockentClient := socketmode.New(
        client,
        socketmode.OptionDebug(true),
        socketmode.OptionLog(log.New(os.Stdout, "socketmode: ", log.Lshortfile|log.LstdFlags)),
    )

    // Implement a graceful shutdown here
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()

    go func(ctx context.Context, client *slack.Client, socketClient *socketmode.Client) {
        for {
            select {
            case <-ctx.Done():
                log.Println("Shutting down socketmode listener")
                return
            case event := <-socketClient.Events:
                switch event.Type {
                // Slack events
                case socketmode.EventTypeEventsAPI:
                    eventsApiEvent, ok := event.Data.(slackevents.EventsAPIEvent)
                    if !ok {
                        log.Printf("Could not type case the event to the EventsAPIEvent: %v\n", event)
                        continue
                    }
                    log.Println("Received API event: ", event.Type)
                    socketClient.Ack(*event.Request)
                    err := handleEventMessage(eventsApiEvent, client)
                    if err != nil {
                        log.Fatal(err)
                    }
                // Slash command
                case socketmode.EventTypeSlashCommand:
                    command, ok := event.Data.(slack.SlashCommand)
                    if !ok {
                        log.Printf("Could not type case the message to a SlashCommand: %v\n", command)
                        continue
                    }
                    log.Println("Received slash event: ", command)
                    socketClient.Ack(*event.Request)
                    err := handleSlashCommand(command, client)
                    if err != nil {
                        log.Fatal(err)
                    }
                default:
                    log.Println("****** Received Event: ", event)
                }
            }
        }
    }(ctx, client, sockentClient)
    sockentClient.Run()
}

manifest.yaml

Versions

rohithkakarla30 commented 4 months ago

It worked for me if i change the value of struct SlashCommand to

type SlashCommand struct {
    Token               string `json:"token"`
    TeamID              string `json:"team_id"`
    TeamDomain          string `json:"team_domain"`
    EnterpriseID        string `json:"enterprise_id,omitempty"`
    EnterpriseName      string `json:"enterprise_name,omitempty"`
    IsEnterpriseInstall bool   `json:"is_enterprise_install,string"`
    ChannelID           string `json:"channel_id"`
    ChannelName         string `json:"channel_name"`
    UserID              string `json:"user_id"`
    UserName            string `json:"user_name"`
    Command             string `json:"command"`
    Text                string `json:"text"`
    ResponseURL         string `json:"response_url"`
    TriggerID           string `json:"trigger_id"`
    APIAppID            string `json:"api_app_id"`
}

Which should not be the case. but not sure. Changed value IsEnterpriseInstall bool json:"is_enterprise_install" -> IsEnterpriseInstall bool json:"is_enterprise_install,string" Reference to line https://github.com/slack-go/slack/blob/v0.12.4/slash.go#L14

nvjustdev commented 4 months ago

Happened to me too

SeniorPomidorro commented 4 months ago

Same problem, rollback to v.0.12.3 is helped

slack-go/slack: v.0.12.4 go1.21.5 darwin/arm64

Event Data:

{parsing slash command: json: cannot unmarshal string into Go struct field SlashCommand.is_enterprise_install of type bool {"envelope_id":"XXXXX","payload":{"token":"XXXXX","team_id":"XXXXX","team_domain":"XXXX","channel_id":"XXXX","channel_name":"lolkek","user_id":"U0XXXXX","user_name":"XXXXX","command":"\/pollsettings","text":"","api_app_id":"XXXXX","is_enterprise_install":"false","response_url":"https:\/\/hooks.slack.com\/commands\/XXXXX\/XXXXXX\/XXXXXX","trigger_id":"XXXXXXX"},"type":"slash_commands","accepts_response_payload":true}}

Code sample:

func (s *Service) eventRouter(ctx context.Context, e socketmode.Event) error {

switch e.Type {
case socketmode.EventTypeSlashCommand:
    s.slack.Ack(e.Request)
    e := e.Data.(slack.SlashCommand)

    switch e.Command {
    case "/test":
        return s.poll.CreateModalView(ctx, &e)
    case "/pollsettings":
        return s.poll.CreatePollSettingsModal(ctx, &e)
    case "/pepefast":
        return s.poll.CreateFast(ctx, &e)
    }

case socketmode.EventTypeInteractive:
    s.slack.Ack(e.Request)
    e := e.Data.(slack.InteractionCallback)

    switch {
    case e.Type == slack.InteractionTypeViewSubmission && (e.View.CallbackID == "poll_modal_view" || e.View.CallbackID == "settings_modal_view"),
        e.Type == slack.InteractionTypeBlockActions && e.View.CallbackID == "settings_modal_view",
        e.Type == slack.InteractionTypeInteractionMessage && strings.Contains(e.CallbackID, "poll"):
        return s.poll.Interactions(ctx, &e)

    }
case socketmode.EventTypeErrorBadMessage:
    fmt.Printf("%s", e.Data)
default:
    if e.Type != "connecting" && e.Type != "connected" && e.Type != "hello" {
        return errors.New(fmt.Sprintf("UNKNOW EVENT TYPE %s", e.Type))
    }
}

return nil

}

parsley42 commented 4 months ago

Sorry about that; probably a straight-forward fix, somebody care to work up a PR? I added a comment to the PR where this was introduced.

talaniz commented 4 months ago

Looks like there's one in that already addresses the comments on the original commit https://github.com/slack-go/slack/pull/1266

parsley42 commented 4 months ago

Just published v0.12.5 which deals w/ bool or string. Thanks to @kpaulisse.

talaniz commented 4 months ago

I've tested in v0.12.5, looks like all is working. Thanks everyone!