Skip to content

Commit

Permalink
feat: use slack socket mode instead of rtm api
Browse files Browse the repository at this point in the history
  • Loading branch information
Ivan Zuev authored and DavyJohnes committed Mar 15, 2022
1 parent 9abbdb5 commit fe92a5d
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 141 deletions.
182 changes: 88 additions & 94 deletions bridge/slack/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"github.com/42wim/matterbridge/bridge/config"
"github.com/42wim/matterbridge/bridge/helper"
"github.com/slack-go/slack"
"github.com/slack-go/slack/slackevents"
"github.com/slack-go/slack/socketmode"
)

// ErrEventIgnored is for events that should be ignored
Expand Down Expand Up @@ -48,65 +50,74 @@ func (b *Bslack) handleSlack() {
}

func (b *Bslack) handleSlackClient(messages chan *config.Message) {
for msg := range b.rtm.IncomingEvents {
if msg.Type != sUserTyping && msg.Type != sHello && msg.Type != sLatencyReport {
b.Log.Debugf("== Receiving event %#v", msg.Data)
}
switch ev := msg.Data.(type) {
case *slack.UserTypingEvent:
if !b.GetBool("ShowUserTyping") {
continue
}
rmsg, err := b.handleTypingEvent(ev)
if err == ErrEventIgnored {
continue
} else if err != nil {
b.Log.Errorf("%#v", err)
continue
for msg := range b.smc.Events {
switch msg.Type {
case socketmode.EventTypeConnected:
if authTest, authErr := b.smc.AuthTest(); authErr == nil {
if botInfo, infoErr := b.smc.GetBotInfo(authTest.BotID); infoErr == nil {
b.si = botInfo

b.channels.populateChannels(true)
b.users.populateUsers(true)
} else {
b.Log.Fatalf("Unable to identify bot user")
}
} else {
b.Log.Fatalf("Unable to identify bot user")
}
case socketmode.EventTypeConnectionError:
ev, _ := msg.Data.(slack.ConnectionErrorEvent)

b.Log.Errorf("Connection failed %#v %#v", ev.Error(), ev.ErrorObj)
case socketmode.EventTypeErrorWriteFailed:
ev, _ := msg.Data.(socketmode.ErrorWriteFailed)

b.Log.Debugf("%#v", ev.Cause.Error())
case socketmode.EventTypeInvalidAuth:
ev, _ := msg.Data.(slack.InvalidAuthEvent)

b.Log.Fatalf("Invalid Token %#v", ev)
case socketmode.EventTypeHello, socketmode.EventTypeConnecting:
continue

case socketmode.EventTypeEventsAPI:
b.smc.Ack(*msg.Request)

eventsAPIEvent, ok := msg.Data.(slackevents.EventsAPIEvent)

if !ok {
b.Log.Debugf("Ignored %+v", eventsAPIEvent)

messages <- rmsg
case *slack.MessageEvent:
if b.skipMessageEvent(ev) {
b.Log.Debugf("Skipped message: %#v", ev)
continue
}
rmsg, err := b.handleMessageEvent(ev)
if err != nil {
b.Log.Errorf("%#v", err)
continue
}
messages <- rmsg
case *slack.FileDeletedEvent:
rmsg, err := b.handleFileDeletedEvent(ev)
if err != nil {
b.Log.Errorf("%#v", err)
continue

switch innerEventData := eventsAPIEvent.InnerEvent.Data.(type) {
case *slackevents.MessageEvent:
if b.skipMessageEvent(innerEventData) {
b.Log.Debugf("Skipped message: %#v", innerEventData)
continue
}
rmsg, err := b.handleMessageEvent(innerEventData)
if err != nil {
b.Log.Errorf("%#v", err)
continue
}
messages <- rmsg
case *slackevents.MemberJoinedChannelEvent:
if innerEventData.User == b.si.UserID {
channel, err := b.smc.GetConversationInfo(innerEventData.Channel, false)

if err != nil {
b.Log.Errorf("Unable to get conversation info for channel %s", innerEventData.Channel)
}

b.channels.registerChannel(*channel)
} else {
b.users.populateUser(innerEventData.User)
}
}
messages <- rmsg
case *slack.OutgoingErrorEvent:
b.Log.Debugf("%#v", ev.Error())
case *slack.ChannelJoinedEvent:
// When we join a channel we update the full list of users as
// well as the information for the channel that we joined as this
// should now tell that we are a member of it.
b.channels.registerChannel(ev.Channel)
case *slack.ConnectedEvent:
b.si = ev.Info
b.channels.populateChannels(true)
b.users.populateUsers(true)
case *slack.InvalidAuthEvent:
b.Log.Fatalf("Invalid Token %#v", ev)
case *slack.ConnectionErrorEvent:
b.Log.Errorf("Connection failed %#v %#v", ev.Error(), ev.ErrorObj)
case *slack.MemberJoinedChannelEvent:
b.users.populateUser(ev.User)
case *slack.HelloEvent, *slack.LatencyReport, *slack.ConnectingEvent:
continue
case *slack.UserChangeEvent:
b.users.invalidateUser(ev.User.ID)
default:
b.Log.Debugf("Unhandled incoming event: %T", ev)
b.Log.Debugf("== Receiving event %#v", msg.Data)
}
}
}
Expand All @@ -127,47 +138,44 @@ func (b *Bslack) handleMatterHook(messages chan *config.Message) {
}

// skipMessageEvent skips event that need to be skipped :-)
func (b *Bslack) skipMessageEvent(ev *slack.MessageEvent) bool {
func (b *Bslack) skipMessageEvent(ev *slackevents.MessageEvent) bool {
switch ev.SubType {
case sChannelLeave, sChannelJoin:
return b.GetBool(noSendJoinConfig)
case sPinnedItem, sUnpinnedItem:
return true
case sChannelTopic, sChannelPurpose:
// Skip the event if our bot/user account changed the topic/purpose
if ev.User == b.si.User.ID {
if ev.BotID == b.si.ID {
return true
}
}

// Check for our callback ID
hasOurCallbackID := false
if len(ev.Blocks.BlockSet) == 1 {
block, ok := ev.Blocks.BlockSet[0].(*slack.SectionBlock)

if len(ev.Attachments) == 1 {
block, ok := ev.Attachments[0].Blocks.BlockSet[0].(*slack.SectionBlock)
hasOurCallbackID = ok && block.BlockID == "matterbridge_"+b.uuid
}

if ev.SubMessage != nil {
if ev.Message != nil {
// It seems ev.SubMessage.Edited == nil when slack unfurls.
// Do not forward these messages. See Github issue #266.
if ev.SubMessage.ThreadTimestamp != ev.SubMessage.Timestamp &&
ev.SubMessage.Edited == nil {
if ev.Message.ThreadTimeStamp != ev.Message.TimeStamp &&
ev.Message.Edited == nil {
return true
}
// see hidden subtypes at https://api.slack.com/events/message
// these messages are sent when we add a message to a thread #709
if ev.SubType == "message_replied" && ev.Hidden {
return true
}
if len(ev.SubMessage.Blocks.BlockSet) == 1 {
block, ok := ev.SubMessage.Blocks.BlockSet[0].(*slack.SectionBlock)

if len(ev.Message.Attachments) == 1 {
block, ok := ev.Message.Attachments[0].Blocks.BlockSet[0].(*slack.SectionBlock)
hasOurCallbackID = ok && block.BlockID == "matterbridge_"+b.uuid
}
}

// Skip any messages that we made ourselves or from 'slackbot' (see #527).
if ev.Username == sSlackBotUser ||
(b.rtm != nil && ev.Username == b.si.User.Name) || hasOurCallbackID {
(b.smc != nil && ev.BotID == b.si.ID) || hasOurCallbackID {
return true
}

Expand All @@ -177,7 +185,7 @@ func (b *Bslack) skipMessageEvent(ev *slack.MessageEvent) bool {
return false
}

func (b *Bslack) filesCached(files []slack.File) bool {
func (b *Bslack) filesCached(files []slackevents.File) bool {
for i := range files {
if !b.fileCached(&files[i]) {
return false
Expand All @@ -202,7 +210,7 @@ func (b *Bslack) filesCached(files []slack.File) bool {
// 5. Handle any attachments of the received event.
// 6. Check that the Matterbridge message that we end up with after at the end of the
// pipeline is valid before sending it to the Matterbridge router.
func (b *Bslack) handleMessageEvent(ev *slack.MessageEvent) (*config.Message, error) {
func (b *Bslack) handleMessageEvent(ev *slackevents.MessageEvent) (*config.Message, error) {
rmsg, err := b.populateReceivedMessage(ev)
if err != nil {
return nil, err
Expand All @@ -222,14 +230,15 @@ func (b *Bslack) handleMessageEvent(ev *slack.MessageEvent) (*config.Message, er
// This is probably a webhook we couldn't resolve.
return nil, fmt.Errorf("message handling resulted in an empty bot message (probably an incoming webhook we couldn't resolve): %#v", ev)
}
if ev.SubMessage != nil {
return nil, fmt.Errorf("message handling resulted in an empty message: %#v with submessage %#v", ev, ev.SubMessage)
if ev.Message != nil {
return nil, fmt.Errorf("message handling resulted in an empty message: %#v with submessage %#v", ev, ev.Message)
}
return nil, fmt.Errorf("message handling resulted in an empty message: %#v", ev)
}
return rmsg, nil
}

// TODO: implement file deletion handle when slack-go library will expose this event
func (b *Bslack) handleFileDeletedEvent(ev *slack.FileDeletedEvent) (*config.Message, error) {
if rawChannel, ok := b.cache.Get(cfileDownloadChannel + ev.FileID); ok {
channel, err := b.channels.getChannelByID(rawChannel.(string))
Expand All @@ -250,7 +259,7 @@ func (b *Bslack) handleFileDeletedEvent(ev *slack.FileDeletedEvent) (*config.Mes
return nil, fmt.Errorf("channel ID for file ID %s not found", ev.FileID)
}

func (b *Bslack) handleStatusEvent(ev *slack.MessageEvent, rmsg *config.Message) bool {
func (b *Bslack) handleStatusEvent(ev *slackevents.MessageEvent, rmsg *config.Message) bool {
switch ev.SubType {
case sChannelJoined, sMemberJoined:
// There's no further processing needed on channel events
Expand All @@ -263,16 +272,16 @@ func (b *Bslack) handleStatusEvent(ev *slack.MessageEvent, rmsg *config.Message)
b.channels.populateChannels(false)
rmsg.Event = config.EventTopicChange
case sMessageChanged:
rmsg.Text = ev.SubMessage.Text
rmsg.Text = ev.Message.Text
// handle deleted thread starting messages
if ev.SubMessage.Text == "This message was deleted." {
if ev.Message.Text == "This message was deleted." {
rmsg.Event = config.EventMsgDelete
return true
}
case sMessageDeleted:
rmsg.Text = config.EventMsgDelete
rmsg.Event = config.EventMsgDelete
rmsg.ID = ev.DeletedTimestamp
rmsg.ID = ev.PreviousMessage.TimeStamp
// If a message is being deleted we do not need to process
// the event any further so we return 'true'.
return true
Expand All @@ -282,7 +291,7 @@ func (b *Bslack) handleStatusEvent(ev *slack.MessageEvent, rmsg *config.Message)
return false
}

func (b *Bslack) handleAttachments(ev *slack.MessageEvent, rmsg *config.Message) {
func (b *Bslack) handleAttachments(ev *slackevents.MessageEvent, rmsg *config.Message) {
// File comments are set by the system (because there is no username given).
if ev.SubType == sFileComment {
rmsg.Username = sSystemUser
Expand Down Expand Up @@ -317,23 +326,8 @@ func (b *Bslack) handleAttachments(ev *slack.MessageEvent, rmsg *config.Message)
}
}

func (b *Bslack) handleTypingEvent(ev *slack.UserTypingEvent) (*config.Message, error) {
if ev.User == b.si.User.ID {
return nil, ErrEventIgnored
}
channelInfo, err := b.channels.getChannelByID(ev.Channel)
if err != nil {
return nil, err
}
return &config.Message{
Channel: channelInfo.Name,
Account: b.Account,
Event: config.EventUserTyping,
}, nil
}

// handleDownloadFile handles file download
func (b *Bslack) handleDownloadFile(rmsg *config.Message, file *slack.File, retry bool) error {
func (b *Bslack) handleDownloadFile(rmsg *config.Message, file *slackevents.File, retry bool) error {
if b.fileCached(file) {
return nil
}
Expand Down Expand Up @@ -395,7 +389,7 @@ func (b *Bslack) handleGetChannelMembers(rmsg *config.Message) bool {
// identically named file but with different content will be uploaded correctly
// (the assumption is that such name collisions will not occur within the given
// timeframes).
func (b *Bslack) fileCached(file *slack.File) bool {
func (b *Bslack) fileCached(file *slackevents.File) bool {
if ts, ok := b.cache.Get("file" + file.ID); ok && time.Since(ts.(time.Time)) < time.Minute {
return true
} else if ts, ok = b.cache.Get("filename" + file.Name); ok && time.Since(ts.(time.Time)) < 10*time.Second {
Expand Down
31 changes: 16 additions & 15 deletions bridge/slack/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ import (
"github.com/42wim/matterbridge/bridge/config"
"github.com/sirupsen/logrus"
"github.com/slack-go/slack"
"github.com/slack-go/slack/slackevents"
)

// populateReceivedMessage shapes the initial Matterbridge message that we will forward to the
// router before we apply message-dependent modifications.
func (b *Bslack) populateReceivedMessage(ev *slack.MessageEvent) (*config.Message, error) {
func (b *Bslack) populateReceivedMessage(ev *slackevents.MessageEvent) (*config.Message, error) {
// Use our own func because rtm.GetChannelInfo doesn't work for private channels.
channel, err := b.channels.getChannelByID(ev.Channel)
if err != nil {
Expand All @@ -24,29 +25,29 @@ func (b *Bslack) populateReceivedMessage(ev *slack.MessageEvent) (*config.Messag
Text: ev.Text,
Channel: channel.Name,
Account: b.Account,
ID: ev.Timestamp,
ID: ev.TimeStamp,
Extra: make(map[string][]interface{}),
ParentID: ev.ThreadTimestamp,
ParentID: ev.ThreadTimeStamp,
Protocol: b.Protocol,
}
if b.useChannelID {
rmsg.Channel = "ID:" + channel.ID
}

// Handle 'edit' messages.
if ev.SubMessage != nil && !b.GetBool(editDisableConfig) {
rmsg.ID = ev.SubMessage.Timestamp
if ev.SubMessage.ThreadTimestamp != ev.SubMessage.Timestamp {
b.Log.Debugf("SubMessage %#v", ev.SubMessage)
rmsg.Text = ev.SubMessage.Text + b.GetString(editSuffixConfig)
if ev.Message != nil && !b.GetBool(editDisableConfig) {
rmsg.ID = ev.Message.TimeStamp
if ev.Message.ThreadTimeStamp != ev.Message.TimeStamp {
b.Log.Debugf("SubMessage %#v", ev.Message)
rmsg.Text = ev.Message.Text + b.GetString(editSuffixConfig)
}
}

// For edits, only submessage has thread ts.
// Ensures edits to threaded messages maintain their prefix hint on the
// unthreaded end.
if ev.SubMessage != nil {
rmsg.ParentID = ev.SubMessage.ThreadTimestamp
if ev.Message != nil {
rmsg.ParentID = ev.Message.ThreadTimeStamp
}

if err = b.populateMessageWithUserInfo(ev, rmsg); err != nil {
Expand All @@ -55,7 +56,7 @@ func (b *Bslack) populateReceivedMessage(ev *slack.MessageEvent) (*config.Messag
return rmsg, err
}

func (b *Bslack) populateMessageWithUserInfo(ev *slack.MessageEvent, rmsg *config.Message) error {
func (b *Bslack) populateMessageWithUserInfo(ev *slackevents.MessageEvent, rmsg *config.Message) error {
if ev.SubType == sMessageDeleted || ev.SubType == sFileComment {
return nil
}
Expand All @@ -71,8 +72,8 @@ func (b *Bslack) populateMessageWithUserInfo(ev *slack.MessageEvent, rmsg *confi
switch {
case ev.User != "":
userID = ev.User
case ev.SubMessage != nil && ev.SubMessage.User != "":
userID = ev.SubMessage.User
case ev.Message != nil && ev.Message.User != "":
userID = ev.Message.User
default:
return nil
}
Expand All @@ -90,15 +91,15 @@ func (b *Bslack) populateMessageWithUserInfo(ev *slack.MessageEvent, rmsg *confi
return nil
}

func (b *Bslack) populateMessageWithBotInfo(ev *slack.MessageEvent, rmsg *config.Message) error {
func (b *Bslack) populateMessageWithBotInfo(ev *slackevents.MessageEvent, rmsg *config.Message) error {
if ev.BotID == "" || b.GetString(outgoingWebhookConfig) != "" {
return nil
}

var err error
var bot *slack.Bot
for {
bot, err = b.rtm.GetBotInfo(ev.BotID)
bot, err = b.smc.GetBotInfo(ev.BotID)
if err == nil {
break
}
Expand Down
Loading

0 comments on commit fe92a5d

Please sign in to comment.