diff --git a/command.go b/command.go index b56f5a8..c1c0792 100644 --- a/command.go +++ b/command.go @@ -16,8 +16,7 @@ type CommandDefinition struct { Handler func(botCtx BotContext, request Request, response ResponseWriter) Interactive func(*Slacker, *socketmode.Event, *slack.InteractionCallback, *socketmode.Request) - // HideHelp will cause this command to not be shown when a user requests - // help. + // HideHelp will hide this command definition from appearing in the `help` results. HideHelp bool } diff --git a/examples/10/example10.go b/examples/10/example10.go index edc37cf..334b84a 100644 --- a/examples/10/example10.go +++ b/examples/10/example10.go @@ -69,13 +69,22 @@ func (r *MyCustomResponseWriter) ReportError(err error, options ...slacker.Repor } } -// Reply send a attachments to the current channel with a message +// Reply send a message to the current channel func (r *MyCustomResponseWriter) Reply(message string, options ...slacker.ReplyOption) error { + ev := r.botCtx.Event() + if ev == nil { + return fmt.Errorf("unable to get message event details") + } + return r.Post(ev.Channel, message, options...) +} + +// Post send a message to a channel +func (r *MyCustomResponseWriter) Post(channel string, message string, options ...slacker.ReplyOption) error { defaults := slacker.NewReplyDefaults(options...) client := r.botCtx.Client() - event := r.botCtx.Event() - if event == nil { + ev := r.botCtx.Event() + if ev == nil { return fmt.Errorf("unable to get message event details") } @@ -84,12 +93,13 @@ func (r *MyCustomResponseWriter) Reply(message string, options ...slacker.ReplyO slack.MsgOptionAttachments(defaults.Attachments...), slack.MsgOptionBlocks(defaults.Blocks...), } + if defaults.ThreadResponse { - opts = append(opts, slack.MsgOptionTS(event.TimeStamp)) + opts = append(opts, slack.MsgOptionTS(ev.TimeStamp)) } _, _, err := client.PostMessage( - event.Channel, + channel, opts..., ) return err diff --git a/go.mod b/go.mod index 66a16aa..229f106 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/shomali11/slacker go 1.14 require ( + github.com/robfig/cron v1.2.0 // indirect github.com/shomali11/commander v0.0.0-20220716022157-b5248c76541a github.com/shomali11/proper v0.0.0-20180607004733-233a9a872c30 github.com/slack-go/slack v0.12.1 diff --git a/go.sum b/go.sum index aa7ffb5..dc71159 100644 --- a/go.sum +++ b/go.sum @@ -9,6 +9,8 @@ github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0U github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ= +github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= github.com/shomali11/commander v0.0.0-20220716022157-b5248c76541a h1:NCmAZOmyqKwf+0KzhY6I6CPndU3qkLRp47RwTyLdMW8= github.com/shomali11/commander v0.0.0-20220716022157-b5248c76541a/go.mod h1:bYyJw/Aj9fK+qoFmRbPJeWsDgq7WGO8f/Qof95qPug4= github.com/shomali11/proper v0.0.0-20180607004733-233a9a872c30 h1:56awf1OXG6Jc2Pk1saojpCzpzkoBvlqecCyNLY+wwkc= diff --git a/message_event.go b/message_event.go index 2692c39..6addb1a 100644 --- a/message_event.go +++ b/message_event.go @@ -1,5 +1,13 @@ package slacker +import ( + "fmt" + + "github.com/slack-go/slack" + "github.com/slack-go/slack/slackevents" + "github.com/slack-go/slack/socketmode" +) + // MessageEvent contains details common to message based events, including the // raw event as returned from Slack along with the corresponding event type. // The struct should be kept minimal and only include data that is commonly @@ -54,3 +62,74 @@ func (e *MessageEvent) IsThread() bool { func (e *MessageEvent) IsBot() bool { return e.BotID != "" } + +// NewMessageEvent creates a new message event structure +func NewMessageEvent(slacker *Slacker, evt interface{}, req *socketmode.Request) *MessageEvent { + var me *MessageEvent + + switch ev := evt.(type) { + case *slackevents.MessageEvent: + me = &MessageEvent{ + Channel: ev.Channel, + ChannelName: getChannelName(slacker, ev.Channel), + User: ev.User, + UserName: getUserName(slacker, ev.User), + Text: ev.Text, + Data: evt, + Type: ev.Type, + TimeStamp: ev.TimeStamp, + ThreadTimeStamp: ev.ThreadTimeStamp, + BotID: ev.BotID, + } + case *slackevents.AppMentionEvent: + me = &MessageEvent{ + Channel: ev.Channel, + ChannelName: getChannelName(slacker, ev.Channel), + User: ev.User, + UserName: getUserName(slacker, ev.User), + Text: ev.Text, + Data: evt, + Type: ev.Type, + TimeStamp: ev.TimeStamp, + ThreadTimeStamp: ev.ThreadTimeStamp, + BotID: ev.BotID, + } + case *slack.SlashCommand: + me = &MessageEvent{ + Channel: ev.ChannelID, + ChannelName: ev.ChannelName, + User: ev.UserID, + UserName: ev.UserName, + Text: fmt.Sprintf("%s %s", ev.Command[1:], ev.Text), + Data: req, + Type: req.Type, + } + } + + // Filter out other bots. At the very least this is needed for MessageEvent + // to prevent the bot from self-triggering and causing loops. However better + // logic should be in place to prevent repeated self-triggering / bot-storms + // if we want to enable this later. + if me.IsBot() { + return nil + } + return me +} + +func getChannelName(slacker *Slacker, channelID string) string { + channel, err := slacker.client.GetConversationInfo(channelID, true) + if err != nil { + fmt.Printf("unable to get channel info for %s: %v\n", channelID, err) + return channelID + } + return channel.Name +} + +func getUserName(slacker *Slacker, userID string) string { + user, err := slacker.client.GetUserInfo(userID) + if err != nil { + fmt.Printf("unable to get user info for %s: %v\n", userID, err) + return userID + } + return user.Name +} diff --git a/response.go b/response.go index 84158fd..2791c0d 100644 --- a/response.go +++ b/response.go @@ -12,6 +12,7 @@ const ( // A ResponseWriter interface is used to respond to an event type ResponseWriter interface { + Post(channel string, message string, options ...ReplyOption) error Reply(text string, options ...ReplyOption) error ReportError(err error, options ...ReportErrorOption) } @@ -35,17 +36,28 @@ func (r *response) ReportError(err error, options ...ReportErrorOption) { opts := []slack.MsgOption{ slack.MsgOptionText(fmt.Sprintf(errorFormat, err.Error()), false), } + if defaults.ThreadResponse { opts = append(opts, slack.MsgOptionTS(ev.TimeStamp)) } + _, _, err = client.PostMessage(ev.Channel, opts...) if err != nil { fmt.Printf("failed posting message: %v\n", err) } } -// Reply send a attachments to the current channel with a message +// Reply send a message to the current channel func (r *response) Reply(message string, options ...ReplyOption) error { + ev := r.botCtx.Event() + if ev == nil { + return fmt.Errorf("unable to get message event details") + } + return r.Post(ev.Channel, message, options...) +} + +// Post send a message to a channel +func (r *response) Post(channel string, message string, options ...ReplyOption) error { defaults := NewReplyDefaults(options...) client := r.botCtx.Client() @@ -59,12 +71,13 @@ func (r *response) Reply(message string, options ...ReplyOption) error { slack.MsgOptionAttachments(defaults.Attachments...), slack.MsgOptionBlocks(defaults.Blocks...), } + if defaults.ThreadResponse { opts = append(opts, slack.MsgOptionTS(ev.TimeStamp)) } _, _, err := client.PostMessage( - ev.Channel, + channel, opts..., ) return err diff --git a/slacker.go b/slacker.go index 826ae0b..53d5b57 100644 --- a/slacker.go +++ b/slacker.go @@ -352,7 +352,7 @@ func (s *Slacker) handleMessageEvent(ctx context.Context, evt interface{}, req * s.responseConstructor = NewResponse } - ev := newMessageEvent(s, evt, req) + ev := NewMessageEvent(s, evt, req) if ev == nil { // event doesn't appear to be a valid message type return @@ -414,76 +414,3 @@ func (s *Slacker) handleMessageEvent(ctx context.Context, evt interface{}, req * } } -func getChannelName(slacker *Slacker, channelID string) string { - channel, err := slacker.client.GetConversationInfo(&slack.GetConversationInfoInput{ - ChannelID: channelID, - IncludeLocale: false, - IncludeNumMembers: false}) - if err != nil { - fmt.Printf("unable to get channel info for %s: %v\n", channelID, err) - return channelID - } - return channel.Name -} - -func getUserName(slacker *Slacker, userID string) string { - user, err := slacker.client.GetUserInfo(userID) - if err != nil { - fmt.Printf("unable to get user info for %s: %v\n", userID, err) - return userID - } - return user.Name -} - -func newMessageEvent(slacker *Slacker, evt interface{}, req *socketmode.Request) *MessageEvent { - var me *MessageEvent - - switch ev := evt.(type) { - case *slackevents.MessageEvent: - me = &MessageEvent{ - Channel: ev.Channel, - ChannelName: getChannelName(slacker, ev.Channel), - User: ev.User, - UserName: getUserName(slacker, ev.User), - Text: ev.Text, - Data: evt, - Type: ev.Type, - TimeStamp: ev.TimeStamp, - ThreadTimeStamp: ev.ThreadTimeStamp, - BotID: ev.BotID, - } - case *slackevents.AppMentionEvent: - me = &MessageEvent{ - Channel: ev.Channel, - ChannelName: getChannelName(slacker, ev.Channel), - User: ev.User, - UserName: getUserName(slacker, ev.User), - Text: ev.Text, - Data: evt, - Type: ev.Type, - TimeStamp: ev.TimeStamp, - ThreadTimeStamp: ev.ThreadTimeStamp, - BotID: ev.BotID, - } - case *slack.SlashCommand: - me = &MessageEvent{ - Channel: ev.ChannelID, - ChannelName: ev.ChannelName, - User: ev.UserID, - UserName: ev.UserName, - Text: fmt.Sprintf("%s %s", ev.Command[1:], ev.Text), - Data: req, - Type: req.Type, - } - } - - // Filter out other bots. At the very least this is needed for MessageEvent - // to prevent the bot from self-triggering and causing loops. However better - // logic should be in place to prevent repeated self-triggering / bot-storms - // if we want to enable this later. - if me.IsBot() { - return nil - } - - return me -}