Skip to content

Commit 5b86262

Browse files
committed
Add support new version of Slack applocation
- Added support of Event API in socketMode - Fix files upload for new applications
1 parent d16645c commit 5b86262

17 files changed

Lines changed: 2575 additions & 22 deletions

bridge/config/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ type Protocol struct {
163163
TeamID string // msteams
164164
TenantID string // msteams
165165
Token string // gitter, slack, discord, api, matrix
166+
AppToken string // slack
166167
Topic string // zulip
167168
URL string // mattermost, slack // DEPRECATED
168169
UseAPI bool // mattermost, slack

bridge/slack/handlers.go

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ import (
99
"github.com/42wim/matterbridge/bridge/config"
1010
"github.com/42wim/matterbridge/bridge/helper"
1111
"github.com/slack-go/slack"
12+
"github.com/slack-go/slack/socketmode"
13+
"github.com/slack-go/slack/slackevents"
14+
"encoding/json"
1215
)
1316

1417
// ErrEventIgnored is for events that should be ignored
@@ -19,6 +22,9 @@ func (b *Bslack) handleSlack() {
1922
if b.GetString(incomingWebhookConfig) != "" && b.GetString(tokenConfig) == "" {
2023
b.Log.Debugf("Choosing webhooks based receiving")
2124
go b.handleMatterHook(messages)
25+
} else if b.GetString(appTokenConfig) != "" && b.GetString(tokenConfig) != "" {
26+
b.Log.Debugf("Choosing socket mode based receiving")
27+
go b.handleSlackClientSocketMode(messages)
2228
} else {
2329
b.Log.Debugf("Choosing token based receiving")
2430
go b.handleSlackClient(messages)
@@ -47,6 +53,135 @@ func (b *Bslack) handleSlack() {
4753
}
4854
}
4955

56+
func (b *Bslack) handleSlackClientSocketMode(messages chan *config.Message) {
57+
58+
for evt := range b.smc.Events {
59+
switch evt.Type {
60+
case socketmode.EventTypeConnecting:
61+
b.Log.Debug("Connecting to Slack with Socket Mode...")
62+
case socketmode.EventTypeConnectionError:
63+
b.Log.Debug("Connection failed. Retrying later...")
64+
case socketmode.EventTypeConnected:
65+
b.Log.Debug("Connected to Slack with Socket Mode.")
66+
if info, err := b.rtm.AuthTest(); err == nil {
67+
b.si = &slack.Info {
68+
User: &slack.UserDetails{
69+
ID: info.UserID,
70+
Name: info.User,
71+
},
72+
Team: &slack.Team{
73+
ID: info.TeamID,
74+
Name: info.Team,
75+
},
76+
}
77+
b.channels.populateChannels(true)
78+
b.users.populateUsers(true)
79+
} else {
80+
b.Log.Fatalf("Get user info error %+v", err)
81+
}
82+
case socketmode.EventTypeEventsAPI:
83+
eventsAPIEvent, ok := evt.Data.(slackevents.EventsAPIEvent)
84+
if !ok {
85+
b.Log.Printf("Ignored %+v\n", evt)
86+
continue
87+
}
88+
89+
b.smc.Ack(*evt.Request)
90+
91+
switch eventsAPIEvent.Type {
92+
case slackevents.CallbackEvent:
93+
innerEvent := eventsAPIEvent.InnerEvent
94+
// b.Log.Debugf("Event received %+v", innerEvent)
95+
switch ev := innerEvent.Data.(type) {
96+
case *slackevents.MessageEvent:
97+
// Workaround for handler compability
98+
evString, _ := json.Marshal(ev)
99+
b.Log.Debugf("Message event: %s", evString)
100+
slackEvent := &slack.MessageEvent{}
101+
if err := json.Unmarshal(evString, &slackEvent); err != nil {
102+
b.Log.Errorf("Skipped message: %#v", err)
103+
continue
104+
}
105+
106+
if b.skipMessageEvent(slackEvent) {
107+
b.Log.Debugf("Skipped message: %#v", slackEvent)
108+
continue
109+
}
110+
rmsg, err := b.handleMessageEvent(slackEvent)
111+
if err != nil {
112+
b.Log.Errorf("%#v", err)
113+
continue
114+
}
115+
messages <- rmsg
116+
case *slackevents.FileDeletedEvent:
117+
slackEvent := &slack.FileDeletedEvent{
118+
Type: ev.Type,
119+
EventTimestamp: ev.EventTimestamp,
120+
FileID: ev.FileID,
121+
}
122+
rmsg, err := b.handleFileDeletedEvent(slackEvent)
123+
if err != nil {
124+
b.Log.Printf("%#v", err)
125+
continue
126+
}
127+
messages <- rmsg
128+
case *slackevents.MemberJoinedChannelEvent:
129+
b.users.populateUser(ev.User)
130+
case *slackevents.UserProfileChangedEvent:
131+
b.users.invalidateUser(ev.User.ID)
132+
133+
// TODO not implemented
134+
// case *slack.ChannelJoinedEvent:
135+
// // When we join a channel we update the full list of users as
136+
// // well as the information for the channel that we joined as this
137+
// // should now tell that we are a member of it.
138+
// b.channels.registerChannel(ev.Channel)
139+
140+
case *slackevents.AppMentionEvent:
141+
default:
142+
b.Log.Debugf("Unhandled incoming event: %T", ev)
143+
}
144+
default:
145+
b.Log.Printf("Unsupported Events API event received: %+v", eventsAPIEvent.Type)
146+
}
147+
case socketmode.EventTypeInteractive:
148+
callback, ok := evt.Data.(slack.InteractionCallback)
149+
if !ok {
150+
b.Log.Printf("Ignored %+v\n", evt)
151+
continue
152+
}
153+
154+
b.Log.Debugf("Interaction skipped: %+v\n", callback)
155+
156+
var payload interface{}
157+
158+
switch callback.Type {
159+
case slack.InteractionTypeBlockActions:
160+
case slack.InteractionTypeShortcut:
161+
case slack.InteractionTypeViewSubmission:
162+
case slack.InteractionTypeDialogSubmission:
163+
default:
164+
165+
}
166+
167+
b.smc.Ack(*evt.Request, payload)
168+
case socketmode.EventTypeSlashCommand:
169+
cmd, ok := evt.Data.(slack.SlashCommand)
170+
if !ok {
171+
b.Log.Printf("Ignored %+v\n", evt)
172+
} else {
173+
b.Log.Debugf("Slash command skipped: %+v", cmd)
174+
}
175+
var payload interface{}
176+
b.smc.Ack(*evt.Request, payload)
177+
case "hello":
178+
continue
179+
default:
180+
b.Log.Errorf("Unexpected event type received: %s\n", evt.Type)
181+
}
182+
}
183+
}
184+
50185
func (b *Bslack) handleSlackClient(messages chan *config.Message) {
51186
for msg := range b.rtm.IncomingEvents {
52187
if msg.Type != sUserTyping && msg.Type != sHello && msg.Type != sLatencyReport {

bridge/slack/slack.go

Lines changed: 62 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
lru "github.com/hashicorp/golang-lru"
1616
"github.com/rs/xid"
1717
"github.com/slack-go/slack"
18+
"github.com/slack-go/slack/socketmode"
1819
)
1920

2021
type Bslack struct {
@@ -24,6 +25,7 @@ type Bslack struct {
2425
mh *matterhook.Client
2526
sc *slack.Client
2627
rtm *slack.RTM
28+
smc *socketmode.Client
2729
si *slack.Info
2830

2931
cache *lru.Cache
@@ -57,6 +59,7 @@ const (
5759
cfileDownloadChannel = "file_download_channel"
5860

5961
tokenConfig = "Token"
62+
appTokenConfig = "AppToken"
6063
incomingWebhookConfig = "WebhookBindAddress"
6164
outgoingWebhookConfig = "WebhookURL"
6265
skipTLSConfig = "SkipTLSVerify"
@@ -109,14 +112,26 @@ func (b *Bslack) Connect() error {
109112
if token := b.GetString(tokenConfig); token != "" {
110113
b.Log.Info("Connecting using token")
111114

112-
b.sc = slack.New(token, slack.OptionDebug(b.GetBool("Debug")))
115+
appToken := b.GetString(appTokenConfig)
116+
b.sc = slack.New(token, slack.OptionDebug(b.GetBool("Debug")), slack.OptionAppLevelToken(appToken))
113117

114118
b.channels = newChannelManager(b.Log, b.sc)
115119
b.users = newUserManager(b.Log, b.sc)
116120

117121
b.rtm = b.sc.NewRTM()
118-
go b.rtm.ManageConnection()
122+
123+
if appToken != "" {
124+
b.smc = socketmode.New(
125+
b.sc,
126+
socketmode.OptionDebug(b.GetBool("Debug")),
127+
)
128+
} else {
129+
go b.rtm.ManageConnection()
130+
}
119131
go b.handleSlack()
132+
if b.smc != nil {
133+
go b.smc.Run()
134+
}
120135
return nil
121136
}
122137

@@ -457,35 +472,60 @@ func (b *Bslack) uploadFile(msg *config.Message, channelID string) (string, erro
457472
// Because the result of the UploadFile is slower than the MessageEvent from slack
458473
// we can't match on the file ID yet, so we have to match on the filename too.
459474
ts := time.Now()
460-
b.Log.Debugf("Adding file %s to cache at %s with timestamp", fi.Name, ts.String())
475+
fSize := int(fi.Size)
476+
if fSize == 0 {
477+
fSize = len(*fi.Data)
478+
}
479+
b.Log.Debugf("Adding file %s to cache at %s with timestamp, size %d", fi.Name, ts.String(), fSize)
461480
b.cache.Add("filename"+fi.Name, ts)
462481
initialComment := fmt.Sprintf("File from %s", msg.Username)
463482
if fi.Comment != "" {
464483
initialComment += fmt.Sprintf(" with comment: %s", fi.Comment)
465484
}
466-
res, err := b.sc.UploadFile(slack.FileUploadParameters{
467-
Reader: bytes.NewReader(*fi.Data),
468-
Filename: fi.Name,
469-
Channels: []string{channelID},
470-
InitialComment: initialComment,
471-
ThreadTimestamp: msg.ParentID,
472-
})
473-
if err != nil {
474-
b.Log.Errorf("uploadfile %#v", err)
475-
return "", err
476-
}
477-
if res.ID != "" {
478-
b.Log.Debugf("Adding file ID %s to cache with timestamp %s", res.ID, ts.String())
479-
b.cache.Add("file"+res.ID, ts)
480485

481-
// search for message id by uploaded file in private/public channels, get thread timestamp from uploaded file
482-
if v, ok := res.Shares.Private[channelID]; ok && len(v) > 0 {
483-
messageID = v[0].Ts
486+
if b.smc != nil {
487+
res, err := b.sc.UploadFileV2(slack.UploadFileV2Parameters{
488+
Reader: bytes.NewReader(*fi.Data),
489+
Filename: fi.Name,
490+
FileSize: fSize,
491+
Channel: channelID,
492+
InitialComment: initialComment,
493+
ThreadTimestamp: msg.ParentID,
494+
})
495+
if err != nil {
496+
b.Log.Errorf("uploadfile %#v", err)
497+
return "", err
498+
}
499+
if res.ID != "" {
500+
b.Log.Debugf("Adding file ID %s to cache with timestamp %s", res.ID, ts.String())
501+
b.cache.Add("file"+res.ID, ts)
502+
messageID = res.ID // TODO
484503
}
485-
if v, ok := res.Shares.Public[channelID]; ok && len(v) > 0 {
486-
messageID = v[0].Ts
504+
} else { // Deprecated version
505+
res, err := b.sc.UploadFile(slack.FileUploadParameters{
506+
Reader: bytes.NewReader(*fi.Data),
507+
Filename: fi.Name,
508+
Channels: []string{channelID},
509+
InitialComment: initialComment,
510+
ThreadTimestamp: msg.ParentID,
511+
})
512+
if err != nil {
513+
b.Log.Errorf("uploadfile %#v", err)
514+
return "", err
515+
}
516+
if res.ID != "" {
517+
b.Log.Debugf("Adding file ID %s to cache with timestamp %s", res.ID, ts.String())
518+
b.cache.Add("file"+res.ID, ts)
519+
// search for message id by uploaded file in private/public channels, get thread timestamp from uploaded file
520+
if v, ok := res.Shares.Private[channelID]; ok && len(v) > 0 {
521+
messageID = v[0].Ts
522+
}
523+
if v, ok := res.Shares.Public[channelID]; ok && len(v) > 0 {
524+
messageID = v[0].Ts
525+
}
487526
}
488527
}
528+
489529
}
490530
return messageID, nil
491531
}

matterbridge.toml.sample

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -644,6 +644,11 @@ PreserveThreading=false
644644
#Use https://api.slack.com/custom-integrations/legacy-tokens
645645
#REQUIRED (when not using webhooks)
646646
Token="yourslacktoken"
647+
#Socket mode token to use modern application API (Events API + socket mode)
648+
#See https://github.com/42wim/matterbridge/issues/2159
649+
#REQUIRED (when not using webhooks or legacy application)
650+
#AppToken="yourslackapptoken"
651+
647652

648653
#Extra slack specific debug info, warning this generates a lot of output.
649654
#OPTIONAL (default false)

vendor/github.com/slack-go/slack/slackevents/action_events.go

Lines changed: 36 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)