2022-01-16 11:06:26 +00:00
package interact
import (
"context"
"fmt"
"log"
"os"
2022-01-19 05:07:25 +00:00
"github.com/google/uuid"
2022-01-16 11:06:26 +00:00
"github.com/sirupsen/logrus"
"github.com/slack-go/slack"
"github.com/slack-go/slack/slackevents"
"github.com/slack-go/slack/socketmode"
)
2022-01-19 05:07:25 +00:00
type SlackReply struct {
// uuid is the unique id of this question
// can be used as the callback id
uuid string
session * SlackSession
client * slack . Client
message string
accessories [ ] * slack . Accessory
}
func ( reply * SlackReply ) Send ( message string ) {
cID , tsID , err := reply . client . PostMessage (
reply . session . ChannelID ,
slack . MsgOptionText ( message , false ) ,
slack . MsgOptionAsUser ( false ) , // Add this if you want that the bot would post message as a user, otherwise it will send response using the default slackbot
)
if err != nil {
logrus . WithError ( err ) . Errorf ( "slack post message error: channel=%s thread=%s" , cID , tsID )
return
}
}
func ( reply * SlackReply ) Message ( message string ) {
reply . message = message
}
// RemoveKeyboard is not supported by Slack
func ( reply * SlackReply ) RemoveKeyboard ( ) { }
func ( reply * SlackReply ) AddButton ( text string , name string , value string ) {
actionID := reply . uuid + ":" + value
reply . accessories = append ( reply . accessories , slack . NewAccessory (
slack . NewButtonBlockElement (
// action id should be unique
actionID ,
value ,
& slack . TextBlockObject {
Type : slack . PlainTextType ,
Text : text ,
} ,
) ,
) )
}
func ( reply * SlackReply ) build ( ) map [ string ] interface { } {
var blocks [ ] slack . Block
blocks = append ( blocks , slack . NewSectionBlock (
& slack . TextBlockObject {
Type : slack . MarkdownType ,
Text : reply . message ,
} ,
nil , // fields
nil , // accessory
nil , // options
) )
blocks = append ( blocks , slack . NewActionBlock ( "" ,
slack . NewButtonBlockElement (
"actionID" ,
"value" ,
slack . NewTextBlockObject (
slack . PlainTextType , "text" , true , false ) ,
) ) )
var payload = map [ string ] interface { } {
"blocks" : blocks ,
}
return payload
}
2022-01-16 11:06:26 +00:00
type SlackSession struct {
BaseSession
2022-01-19 05:07:25 +00:00
ChannelID string
UserID string
// questions is used to store the questions that we added in the reply
// the key is the client generated callback id
questions map [ string ] interface { }
}
func NewSlackSession ( ) * SlackSession {
return & SlackSession {
questions : make ( map [ string ] interface { } ) ,
}
}
func ( s * SlackSession ) ID ( ) string {
return fmt . Sprintf ( "%s-%s" , s . UserID , s . ChannelID )
2022-01-16 11:06:26 +00:00
}
type SlackSessionMap map [ int64 ] * SlackSession
//go:generate callbackgen -type Slack
type Slack struct {
client * slack . Client
socket * socketmode . Client
sessions SlackSessionMap
commands [ ] * Command
// textMessageResponder is used for interact to register its message handler
textMessageResponder Responder
authorizedCallbacks [ ] func ( userSession * SlackSession )
2022-01-19 05:07:25 +00:00
eventsApiCallbacks [ ] func ( slackevents . EventsAPIEvent )
2022-01-16 11:06:26 +00:00
}
func NewSlack ( client * slack . Client ) * Slack {
socket := socketmode . New (
client ,
socketmode . OptionDebug ( true ) ,
socketmode . OptionLog (
log . New ( os . Stdout , "socketmode: " ,
log . Lshortfile | log . LstdFlags ) ) ,
)
return & Slack {
client : client ,
socket : socket ,
}
}
func ( s * Slack ) SetTextMessageResponder ( responder Responder ) {
s . textMessageResponder = responder
}
func ( s * Slack ) AddCommand ( command * Command , responder Responder ) {
s . commands = append ( s . commands , command )
}
func ( s * Slack ) listen ( ) {
for evt := range s . socket . Events {
switch evt . Type {
case socketmode . EventTypeConnecting :
fmt . Println ( "Connecting to Slack with Socket Mode..." )
case socketmode . EventTypeConnectionError :
fmt . Println ( "Connection failed. Retrying later..." )
case socketmode . EventTypeConnected :
fmt . Println ( "Connected to Slack with Socket Mode." )
case socketmode . EventTypeEventsAPI :
eventsAPIEvent , ok := evt . Data . ( slackevents . EventsAPIEvent )
if ! ok {
2022-01-19 05:07:25 +00:00
logrus . Debugf ( "ignored %+v" , evt )
2022-01-16 11:06:26 +00:00
continue
}
2022-01-19 05:07:25 +00:00
logrus . Debugf ( "event received: %+v" , eventsAPIEvent )
2022-01-16 11:06:26 +00:00
s . socket . Ack ( * evt . Request )
2022-01-19 05:07:25 +00:00
s . EmitEventsApi ( eventsAPIEvent )
2022-01-16 11:06:26 +00:00
switch eventsAPIEvent . Type {
case slackevents . CallbackEvent :
innerEvent := eventsAPIEvent . InnerEvent
switch ev := innerEvent . Data . ( type ) {
case * slackevents . AppMentionEvent :
_ , _ , err := s . client . PostMessage ( ev . Channel , slack . MsgOptionText ( "Yes, hello." , false ) )
if err != nil {
fmt . Printf ( "failed posting message: %v" , err )
}
case * slackevents . MemberJoinedChannelEvent :
fmt . Printf ( "user %q joined to channel %q" , ev . User , ev . Channel )
}
default :
s . socket . Debugf ( "unsupported Events API event received" )
}
case socketmode . EventTypeInteractive :
callback , ok := evt . Data . ( slack . InteractionCallback )
if ! ok {
2022-01-19 05:07:25 +00:00
logrus . Debugf ( "ignored %+v" , evt )
2022-01-16 11:06:26 +00:00
continue
}
2022-01-19 05:07:25 +00:00
logrus . Debugf ( "interaction received: %+v" , callback )
2022-01-16 11:06:26 +00:00
var payload interface { }
switch callback . Type {
case slack . InteractionTypeBlockActions :
// See https://api.slack.com/apis/connections/socket-implement#button
2022-01-19 05:07:25 +00:00
logrus . Debugf ( "button clicked!" )
2022-01-16 11:06:26 +00:00
case slack . InteractionTypeShortcut :
case slack . InteractionTypeViewSubmission :
// See https://api.slack.com/apis/connections/socket-implement#modal
case slack . InteractionTypeDialogSubmission :
default :
}
s . socket . Ack ( * evt . Request , payload )
2022-01-19 05:07:25 +00:00
2022-01-16 11:06:26 +00:00
case socketmode . EventTypeSlashCommand :
cmd , ok := evt . Data . ( slack . SlashCommand )
if ! ok {
2022-01-19 05:07:25 +00:00
logrus . Debugf ( "ignored %+v" , evt )
2022-01-16 11:06:26 +00:00
continue
}
2022-01-19 05:07:25 +00:00
logrus . Debugf ( "slash command received: %+v" , cmd )
session := s . newSession ( evt )
reply := s . newReply ( session )
if err := s . textMessageResponder ( session , "" , reply ) ; err != nil {
continue
}
2022-01-16 11:06:26 +00:00
2022-01-19 05:07:25 +00:00
payload := reply . build ( )
2022-01-16 11:06:26 +00:00
s . socket . Ack ( * evt . Request , payload )
default :
2022-01-19 05:07:25 +00:00
logrus . Debugf ( "unexpected event type received: %s" , evt . Type )
2022-01-16 11:06:26 +00:00
}
}
}
2022-01-19 05:07:25 +00:00
func ( s * Slack ) newSession ( evt socketmode . Event ) * SlackSession {
return NewSlackSession ( )
}
func ( s * Slack ) newReply ( session * SlackSession ) * SlackReply {
return & SlackReply {
uuid : uuid . New ( ) . String ( ) ,
session : session ,
}
}
2022-01-16 11:06:26 +00:00
func ( s * Slack ) Start ( ctx context . Context ) {
go s . listen ( )
2022-01-19 05:07:25 +00:00
if err := s . socket . Run ( ) ; err != nil {
2022-01-16 11:06:26 +00:00
logrus . WithError ( err ) . Errorf ( "slack socketmode error" )
}
}