Add support new version of Slack applocation
- Added support of Event API in socketMode - Fix files upload for new applications
This commit is contained in:
63
vendor/github.com/slack-go/slack/socketmode/client.go
generated
vendored
Normal file
63
vendor/github.com/slack-go/slack/socketmode/client.go
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
package socketmode
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/slack-go/slack"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
type ConnectedEvent struct {
|
||||
ConnectionCount int // 1 = first time, 2 = second time
|
||||
Info *slack.SocketModeConnection
|
||||
}
|
||||
|
||||
type DebugInfo struct {
|
||||
// Host is the name of the host name on the Slack end, that can be something like `applink-7fc4fdbb64-4x5xq`
|
||||
Host string `json:"host"`
|
||||
|
||||
// `hello` type only
|
||||
BuildNumber int `json:"build_number"`
|
||||
ApproximateConnectionTime int `json:"approximate_connection_time"`
|
||||
}
|
||||
|
||||
type ConnectionInfo struct {
|
||||
AppID string `json:"app_id"`
|
||||
}
|
||||
|
||||
type SocketModeMessagePayload struct {
|
||||
Event json.RawMessage `json:"event"`
|
||||
}
|
||||
|
||||
// Client is a Socket Mode client that allows programs to use [Events API](https://api.slack.com/events-api)
|
||||
// and [interactive components](https://api.slack.com/interactivity) over WebSocket.
|
||||
// Please see [Intro to Socket Mode](https://api.slack.com/apis/connections/socket) for more information
|
||||
// on Socket Mode.
|
||||
//
|
||||
// The implementation is highly inspired by https://www.npmjs.com/package/@slack/socket-mode,
|
||||
// but the structure and the design has been adapted as much as possible to that of our RTM client for consistency
|
||||
// within the library.
|
||||
//
|
||||
// You can instantiate the socket mode client with
|
||||
// Client's New() and call Run() to start it. Please see examples/socketmode for the usage.
|
||||
type Client struct {
|
||||
// Client is the main API, embedded
|
||||
slack.Client
|
||||
|
||||
// maxPingInterval is the maximum duration elapsed after the last WebSocket PING sent from Slack
|
||||
// until Client considers the WebSocket connection is dead and needs to be reopened.
|
||||
maxPingInterval time.Duration
|
||||
|
||||
// Connection life-cycle
|
||||
Events chan Event
|
||||
socketModeResponses chan *Response
|
||||
|
||||
// dialer is a gorilla/websocket Dialer. If nil, use the default
|
||||
// Dialer.
|
||||
dialer *websocket.Dialer
|
||||
|
||||
debug bool
|
||||
log ilogger
|
||||
}
|
||||
30
vendor/github.com/slack-go/slack/socketmode/event.go
generated
vendored
Normal file
30
vendor/github.com/slack-go/slack/socketmode/event.go
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
package socketmode
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
// Event is the event sent to the consumer of Client
|
||||
type Event struct {
|
||||
Type EventType
|
||||
Data interface{}
|
||||
|
||||
// Request is the json-decoded raw WebSocket message that is received via the Slack Socket Mode
|
||||
// WebSocket connection.
|
||||
Request *Request
|
||||
}
|
||||
|
||||
type ErrorBadMessage struct {
|
||||
Cause error
|
||||
Message json.RawMessage
|
||||
}
|
||||
|
||||
type ErrorWriteFailed struct {
|
||||
Cause error
|
||||
Response *Response
|
||||
}
|
||||
|
||||
type errorRequestedDisconnect struct {
|
||||
}
|
||||
|
||||
func (e errorRequestedDisconnect) Error() string {
|
||||
return "disconnection requested: Slack requested us to disconnect"
|
||||
}
|
||||
51
vendor/github.com/slack-go/slack/socketmode/log.go
generated
vendored
Normal file
51
vendor/github.com/slack-go/slack/socketmode/log.go
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
package socketmode
|
||||
|
||||
import "fmt"
|
||||
|
||||
// TODO merge logger, ilogger, and internalLogger with the top-level package's equivalents
|
||||
|
||||
// logger is a logger interface compatible with both stdlib and some
|
||||
// 3rd party loggers.
|
||||
type logger interface {
|
||||
Output(int, string) error
|
||||
}
|
||||
|
||||
// ilogger represents the internal logging api we use.
|
||||
type ilogger interface {
|
||||
logger
|
||||
Print(...interface{})
|
||||
Printf(string, ...interface{})
|
||||
Println(...interface{})
|
||||
}
|
||||
|
||||
// internalLog implements the additional methods used by our internal logging.
|
||||
type internalLog struct {
|
||||
logger
|
||||
}
|
||||
|
||||
// Println replicates the behaviour of the standard logger.
|
||||
func (t internalLog) Println(v ...interface{}) {
|
||||
t.Output(2, fmt.Sprintln(v...))
|
||||
}
|
||||
|
||||
// Printf replicates the behaviour of the standard logger.
|
||||
func (t internalLog) Printf(format string, v ...interface{}) {
|
||||
t.Output(2, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
// Print replicates the behaviour of the standard logger.
|
||||
func (t internalLog) Print(v ...interface{}) {
|
||||
t.Output(2, fmt.Sprint(v...))
|
||||
}
|
||||
|
||||
func (smc *Client) Debugf(format string, v ...interface{}) {
|
||||
if smc.debug {
|
||||
smc.log.Output(2, fmt.Sprintf(format, v...))
|
||||
}
|
||||
}
|
||||
|
||||
func (smc *Client) Debugln(v ...interface{}) {
|
||||
if smc.debug {
|
||||
smc.log.Output(2, fmt.Sprintln(v...))
|
||||
}
|
||||
}
|
||||
38
vendor/github.com/slack-go/slack/socketmode/request.go
generated
vendored
Normal file
38
vendor/github.com/slack-go/slack/socketmode/request.go
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
package socketmode
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
// Request maps to the content of each WebSocket message received via a Socket Mode WebSocket connection
|
||||
//
|
||||
// We call this a "request" rather than e.g. a WebSocket message or an Socket Mode "event" following python-slack-sdk:
|
||||
//
|
||||
// https://github.com/slackapi/python-slack-sdk/blob/3f1c4c6e27bf7ee8af57699b2543e6eb7848bcf9/slack_sdk/socket_mode/request.py#L6
|
||||
//
|
||||
// We know that node-slack-sdk calls it an "event", that makes it hard for us to distinguish our client's own event
|
||||
// that wraps both internal events and Socket Mode "events", vs node-slack-sdk's is for the latter only.
|
||||
//
|
||||
// https://github.com/slackapi/node-slack-sdk/blob/main/packages/socket-mode/src/SocketModeClient.ts#L537
|
||||
type Request struct {
|
||||
Type string `json:"type"`
|
||||
|
||||
// `hello` type only
|
||||
NumConnections int `json:"num_connections"`
|
||||
ConnectionInfo ConnectionInfo `json:"connection_info"`
|
||||
|
||||
// `disconnect` type only
|
||||
|
||||
// Reason can be "warning" or else
|
||||
Reason string `json:"reason"`
|
||||
|
||||
// `hello` and `disconnect` types only
|
||||
DebugInfo DebugInfo `json:"debug_info"`
|
||||
|
||||
// `events_api` type only
|
||||
EnvelopeID string `json:"envelope_id"`
|
||||
// TODO Can it really be a non-object type?
|
||||
// See https://github.com/slackapi/python-slack-sdk/blob/3f1c4c6e27bf7ee8af57699b2543e6eb7848bcf9/slack_sdk/socket_mode/request.py#L26-L31
|
||||
Payload json.RawMessage `json:"payload"`
|
||||
AcceptsResponsePayload bool `json:"accepts_response_payload"`
|
||||
RetryAttempt int `json:"retry_attempt"`
|
||||
RetryReason string `json:"retry_reason"`
|
||||
}
|
||||
6
vendor/github.com/slack-go/slack/socketmode/response.go
generated
vendored
Normal file
6
vendor/github.com/slack-go/slack/socketmode/response.go
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
package socketmode
|
||||
|
||||
type Response struct {
|
||||
EnvelopeID string `json:"envelope_id"`
|
||||
Payload interface{} `json:"payload,omitempty"`
|
||||
}
|
||||
633
vendor/github.com/slack-go/slack/socketmode/socket_mode_managed_conn.go
generated
vendored
Normal file
633
vendor/github.com/slack-go/slack/socketmode/socket_mode_managed_conn.go
generated
vendored
Normal file
@@ -0,0 +1,633 @@
|
||||
package socketmode
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
|
||||
"github.com/slack-go/slack"
|
||||
"github.com/slack-go/slack/internal/backoff"
|
||||
"github.com/slack-go/slack/internal/timex"
|
||||
"github.com/slack-go/slack/slackevents"
|
||||
)
|
||||
|
||||
// Run is a blocking function that connects the Slack Socket Mode API and handles all incoming
|
||||
// requests and outgoing responses.
|
||||
//
|
||||
// The consumer of the Client and this function should read the Client.Events channel to receive
|
||||
// `socketmode.Event`s that includes the client-specific events that may or may not wrap Socket Mode requests.
|
||||
//
|
||||
// Note that this function automatically reconnect on requested by Slack through a `disconnect` message.
|
||||
// This function exists with an error only when a reconnection is failued due to some reason.
|
||||
// If you want to retry even on reconnection failure, you'd need to write your own wrapper for this function
|
||||
// to do so.
|
||||
func (smc *Client) Run() error {
|
||||
return smc.RunContext(context.TODO())
|
||||
}
|
||||
|
||||
// RunContext is a blocking function that connects the Slack Socket Mode API and handles all incoming
|
||||
// requests and outgoing responses.
|
||||
//
|
||||
// The consumer of the Client and this function should read the Client.Events channel to receive
|
||||
// `socketmode.Event`s that includes the client-specific events that may or may not wrap Socket Mode requests.
|
||||
//
|
||||
// Note that this function automatically reconnect on requested by Slack through a `disconnect` message.
|
||||
// This function exists with an error only when a reconnection is failued due to some reason.
|
||||
// If you want to retry even on reconnection failure, you'd need to write your own wrapper for this function
|
||||
// to do so.
|
||||
func (smc *Client) RunContext(ctx context.Context) error {
|
||||
for connectionCount := 0; ; connectionCount++ {
|
||||
if err := smc.run(ctx, connectionCount); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Continue and run the loop again to reconnect
|
||||
}
|
||||
}
|
||||
|
||||
func (smc *Client) run(ctx context.Context, connectionCount int) error {
|
||||
messages := make(chan json.RawMessage, 1)
|
||||
|
||||
pingChan := make(chan time.Time, 1)
|
||||
pingHandler := func(_ string) error {
|
||||
select {
|
||||
case pingChan <- time.Now():
|
||||
default:
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start trying to connect
|
||||
// the returned err is already passed onto the Events channel
|
||||
//
|
||||
// We also configures an additional ping handler for the deadmanTimer that triggers a timeout when
|
||||
// Slack did not send us WebSocket PING for more than Client.maxPingInterval.
|
||||
// We can use `<-smc.pingTimeout.C` to wait for the timeout.
|
||||
info, conn, err := smc.connect(ctx, connectionCount, pingHandler)
|
||||
if err != nil {
|
||||
// when the connection is unsuccessful its fatal, and we need to bail out.
|
||||
smc.Debugf("Failed to connect with Socket Mode on try %d: %s", connectionCount, err)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
smc.sendEvent(ctx, newEvent(EventTypeConnected, &ConnectedEvent{
|
||||
ConnectionCount: connectionCount,
|
||||
Info: info,
|
||||
}))
|
||||
|
||||
smc.Debugf("WebSocket connection succeeded on try %d", connectionCount)
|
||||
|
||||
// We're now connected so we can set up listeners
|
||||
|
||||
wg := new(sync.WaitGroup)
|
||||
// sendErr relies on the buffer of 1 here
|
||||
errc := make(chan error, 1)
|
||||
sendErr := func(err error) {
|
||||
select {
|
||||
case errc <- err:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer cancel()
|
||||
|
||||
// The response sender sends Socket Mode responses over the WebSocket conn
|
||||
if err := smc.runResponseSender(ctx, conn); err != nil {
|
||||
sendErr(err)
|
||||
}
|
||||
}()
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer cancel()
|
||||
|
||||
// The handler reads Socket Mode requests, and enqueues responses for sending by the response sender
|
||||
if err := smc.runRequestHandler(ctx, messages); err != nil {
|
||||
sendErr(err)
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer cancel()
|
||||
// We close messages here as it is the producer for the channel.
|
||||
defer close(messages)
|
||||
|
||||
// The receiver reads WebSocket messages, and enqueues parsed Socket Mode requests to be handled by
|
||||
// the request handler
|
||||
if err := smc.runMessageReceiver(ctx, conn, messages); err != nil {
|
||||
sendErr(err)
|
||||
}
|
||||
}()
|
||||
|
||||
wg.Add(1)
|
||||
go func(pingInterval time.Duration) {
|
||||
defer wg.Done()
|
||||
defer func() {
|
||||
// Detect when the connection is dead and try close connection.
|
||||
if err := conn.Close(); err != nil {
|
||||
smc.Debugf("Failed to close connection: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
done := ctx.Done()
|
||||
var lastPing time.Time
|
||||
|
||||
// More efficient than constantly resetting a timer w/ Stop+Reset
|
||||
ticker := time.NewTicker(pingInterval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
|
||||
case lastPing = <-pingChan:
|
||||
// This case gets the time of the last ping.
|
||||
// If this case never fires then the pingHandler was never called
|
||||
// in which case lastPing is the zero time.Time value, and will 'fail'
|
||||
// the next tick, causing us to exit.
|
||||
|
||||
case now := <-ticker.C:
|
||||
// Our last ping is older than our interval
|
||||
if now.Sub(lastPing) > pingInterval {
|
||||
sendErr(errors.New("ping timeout: Slack did not send us WebSocket PING for more than Client.maxInterval"))
|
||||
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}(smc.maxPingInterval)
|
||||
|
||||
wg.Wait()
|
||||
|
||||
select {
|
||||
case err = <-errc:
|
||||
// Get buffered error
|
||||
default:
|
||||
// Or nothing if they all exited nil
|
||||
}
|
||||
|
||||
if errors.Is(err, context.Canceled) {
|
||||
return err
|
||||
}
|
||||
|
||||
// wg.Wait() finishes only after any of the above go routines finishes and cancels the
|
||||
// context, allowing the other threads to shut down gracefully.
|
||||
// Also, we can expect our (first)err to be not nil, as goroutines can finish only on error.
|
||||
smc.Debugf("Reconnecting due to %v", err)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// connect attempts to connect to the slack websocket API. It handles any
|
||||
// errors that occur while connecting and will return once a connection
|
||||
// has been successfully opened.
|
||||
func (smc *Client) connect(ctx context.Context, connectionCount int, additionalPingHandler func(string) error) (*slack.SocketModeConnection, *websocket.Conn, error) {
|
||||
const (
|
||||
errInvalidAuth = "invalid_auth"
|
||||
errInactiveAccount = "account_inactive"
|
||||
errMissingAuthToken = "not_authed"
|
||||
errTokenRevoked = "token_revoked"
|
||||
)
|
||||
|
||||
// used to provide exponential backoff wait time with jitter before trying
|
||||
// to connect to slack again
|
||||
boff := &backoff.Backoff{
|
||||
Max: 5 * time.Minute,
|
||||
}
|
||||
|
||||
for {
|
||||
var (
|
||||
backoff time.Duration
|
||||
)
|
||||
|
||||
// send connecting event
|
||||
smc.sendEvent(ctx, newEvent(EventTypeConnecting, &slack.ConnectingEvent{
|
||||
Attempt: boff.Attempts() + 1,
|
||||
ConnectionCount: connectionCount,
|
||||
}))
|
||||
|
||||
// attempt to start the connection
|
||||
info, conn, err := smc.openAndDial(ctx, additionalPingHandler)
|
||||
if err == nil {
|
||||
return info, conn, nil
|
||||
}
|
||||
|
||||
// check for fatal errors
|
||||
switch err.Error() {
|
||||
case errInvalidAuth, errInactiveAccount, errMissingAuthToken, errTokenRevoked:
|
||||
smc.Debugf("invalid auth when connecting with SocketMode: %s", err)
|
||||
return nil, nil, err
|
||||
default:
|
||||
}
|
||||
|
||||
var (
|
||||
actual slack.StatusCodeError
|
||||
rlError *slack.RateLimitedError
|
||||
)
|
||||
|
||||
if errors.As(err, &actual) && actual.Code == http.StatusNotFound {
|
||||
smc.Debugf("invalid auth when connecting with Socket Mode: %s", err)
|
||||
smc.sendEvent(ctx, newEvent(EventTypeInvalidAuth, &slack.InvalidAuthEvent{}))
|
||||
|
||||
return nil, nil, err
|
||||
} else if errors.As(err, &rlError) {
|
||||
backoff = rlError.RetryAfter
|
||||
}
|
||||
|
||||
// If we check for errors.Is(err, context.Canceled) here and
|
||||
// return early then we don't send the Event below that some users
|
||||
// may already rely on; ie a behavior change.
|
||||
|
||||
backoff = timex.Max(backoff, boff.Duration())
|
||||
// any other errors are treated as recoverable and we try again after
|
||||
// sending the event along the Events channel
|
||||
smc.sendEvent(ctx, newEvent(EventTypeConnectionError, &slack.ConnectionErrorEvent{
|
||||
Attempt: boff.Attempts(),
|
||||
Backoff: backoff,
|
||||
ErrorObj: err,
|
||||
}))
|
||||
|
||||
// get time we should wait before attempting to connect again
|
||||
smc.Debugf("reconnection %d failed: %s reconnecting in %v\n", boff.Attempts(), err, backoff)
|
||||
|
||||
// wait for one of the following to occur,
|
||||
// backoff duration has elapsed, disconnectCh is signalled, or
|
||||
// the smc finishes disconnecting.
|
||||
timer := time.NewTimer(backoff)
|
||||
select {
|
||||
case <-timer.C: // retry after the backoff.
|
||||
case <-ctx.Done():
|
||||
timer.Stop()
|
||||
return nil, nil, ctx.Err()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// openAndDial attempts to open a Socket Mode connection and dial to the connection endpoint using WebSocket.
|
||||
// It returns the full information returned by the "apps.connections.open" method on the
|
||||
// Slack API.
|
||||
func (smc *Client) openAndDial(ctx context.Context, additionalPingHandler func(string) error) (info *slack.SocketModeConnection, _ *websocket.Conn, err error) {
|
||||
var (
|
||||
url string
|
||||
)
|
||||
|
||||
smc.Debugf("Starting SocketMode")
|
||||
info, url, err = smc.OpenContext(ctx)
|
||||
|
||||
if err != nil {
|
||||
smc.Debugf("Failed to start or connect with SocketMode: %s", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
smc.Debugf("Dialing to websocket on url %s", url)
|
||||
// Only use HTTPS for connections to prevent MITM attacks on the connection.
|
||||
upgradeHeader := http.Header{}
|
||||
upgradeHeader.Add("Origin", "https://api.slack.com")
|
||||
dialer := websocket.DefaultDialer
|
||||
if smc.dialer != nil {
|
||||
dialer = smc.dialer
|
||||
}
|
||||
conn, _, err := dialer.DialContext(ctx, url, upgradeHeader)
|
||||
if err != nil {
|
||||
smc.Debugf("Failed to dial to the websocket: %s", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
if additionalPingHandler == nil {
|
||||
additionalPingHandler = func(_ string) error { return nil }
|
||||
}
|
||||
|
||||
conn.SetPingHandler(func(appData string) error {
|
||||
if err := additionalPingHandler(appData); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
smc.handlePing(conn, appData)
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
// We don't need to conn.SetCloseHandler because the default handler is effective enough that
|
||||
// it sends back the CLOSE message to the server and let conn.ReadJSON() fail with CloseError.
|
||||
// The CloseError must be handled normally in our receiveMessagesInto function.
|
||||
//conn.SetCloseHandler(func(code int, text string) error {
|
||||
// ...
|
||||
// })
|
||||
|
||||
return info, conn, err
|
||||
}
|
||||
|
||||
// runResponseSender runs the handler that reads Socket Mode responses enqueued onto Client.socketModeResponses channel
|
||||
// and sends them one by one over the WebSocket connection.
|
||||
// Gorilla WebSocket is not goroutine safe hence this needs to be the single place you write to the WebSocket connection.
|
||||
func (smc *Client) runResponseSender(ctx context.Context, conn *websocket.Conn) error {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
// 3. listen for messages that need to be sent
|
||||
case res := <-smc.socketModeResponses:
|
||||
smc.Debugf("Sending Socket Mode response with envelope ID %q: %v", res.EnvelopeID, res)
|
||||
|
||||
if err := unsafeWriteSocketModeResponse(conn, res); err != nil {
|
||||
smc.sendEvent(ctx, newEvent(EventTypeErrorWriteFailed, &ErrorWriteFailed{
|
||||
Cause: err,
|
||||
Response: res,
|
||||
}))
|
||||
}
|
||||
|
||||
smc.Debugf("Finished sending Socket Mode response with envelope ID %q", res.EnvelopeID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// runRequestHandler is a blocking function that runs the Socket Mode request receiver.
|
||||
//
|
||||
// It reads WebSocket messages sent from Slack's Socket Mode WebSocket connection,
|
||||
// parses them as Socket Mode requests, and processes them and optionally emit our own events into Client.Events channel.
|
||||
func (smc *Client) runRequestHandler(ctx context.Context, websocket chan json.RawMessage) error {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case message, ok := <-websocket:
|
||||
if !ok {
|
||||
// The producer closed the channel because it encountered an error (or panic),
|
||||
// we need only return.
|
||||
return nil
|
||||
}
|
||||
|
||||
smc.Debugf("Received WebSocket message: %s", message)
|
||||
|
||||
// listen for incoming messages that need to be parsed
|
||||
evt, err := smc.parseEvent(message)
|
||||
if err != nil {
|
||||
smc.sendEvent(ctx, newEvent(EventTypeErrorBadMessage, &ErrorBadMessage{
|
||||
Cause: err,
|
||||
Message: message,
|
||||
}))
|
||||
} else if evt != nil {
|
||||
if evt.Type == EventTypeDisconnect {
|
||||
// We treat the `disconnect` request from Slack as an error internally,
|
||||
// so that we can tell the consumer of this function to reopen the connection on it.
|
||||
return errorRequestedDisconnect{}
|
||||
}
|
||||
|
||||
smc.sendEvent(ctx, *evt)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// runMessageReceiver monitors the Socket Mode opened WebSocket connection for any incoming
|
||||
// messages. It pushes the raw events into the channel.
|
||||
// The receiver runs until the context is closed.
|
||||
func (smc *Client) runMessageReceiver(ctx context.Context, conn *websocket.Conn, sink chan json.RawMessage) error {
|
||||
for {
|
||||
if err := smc.receiveMessagesInto(ctx, conn, sink); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// unsafeWriteSocketModeResponse sends a WebSocket message back to Slack.
|
||||
// WARNING: Call to this function must be serialized!
|
||||
//
|
||||
// Here's why - Gorilla WebSocket's Writes functions are not concurrency-safe.
|
||||
// That is, we must serialize all the writes to it with e.g. a goroutine or mutex.
|
||||
// We intentionally chose to use goroutine, which makes it harder to propagate write errors to the caller,
|
||||
// but is more computationally efficient.
|
||||
//
|
||||
// See the below for more information on this topic:
|
||||
// https://stackoverflow.com/questions/43225340/how-to-ensure-concurrency-in-golang-gorilla-websocket-package
|
||||
func unsafeWriteSocketModeResponse(conn *websocket.Conn, res *Response) error {
|
||||
// set a write deadline on the connection
|
||||
if err := conn.SetWriteDeadline(time.Now().Add(10 * time.Second)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove write deadline regardless of WriteJSON succeeds or not
|
||||
defer conn.SetWriteDeadline(time.Time{})
|
||||
|
||||
return conn.WriteJSON(res)
|
||||
}
|
||||
|
||||
func newEvent(tpe EventType, data interface{}, req ...*Request) Event {
|
||||
evt := Event{Type: tpe, Data: data}
|
||||
|
||||
if len(req) > 0 {
|
||||
evt.Request = req[0]
|
||||
}
|
||||
|
||||
return evt
|
||||
}
|
||||
|
||||
// Ack acknowledges the Socket Mode request with the payload.
|
||||
//
|
||||
// This tells Slack that the we have received the request denoted by the envelope ID,
|
||||
// by sending back the envelope ID over the WebSocket connection.
|
||||
func (smc *Client) Ack(req Request, payload ...interface{}) {
|
||||
var pld interface{}
|
||||
if len(payload) > 0 {
|
||||
pld = payload[0]
|
||||
}
|
||||
|
||||
smc.AckCtx(context.TODO(), req.EnvelopeID, pld)
|
||||
}
|
||||
|
||||
// AckCtx acknowledges the Socket Mode request envelope ID with the payload.
|
||||
//
|
||||
// This tells Slack that the we have received the request denoted by the request (envelope) ID,
|
||||
// by sending back the ID over the WebSocket connection.
|
||||
func (smc *Client) AckCtx(ctx context.Context, reqID string, payload interface{}) error {
|
||||
return smc.SendCtx(ctx, Response{
|
||||
EnvelopeID: reqID,
|
||||
Payload: payload,
|
||||
})
|
||||
}
|
||||
|
||||
// Send sends the Socket Mode response over a WebSocket connection.
|
||||
// This is usually used for acknowledging requests, but if you need more control over Client.Ack().
|
||||
// It's normally recommended to use Client.Ack() instead of this.
|
||||
func (smc *Client) Send(res Response) {
|
||||
smc.SendCtx(context.TODO(), res)
|
||||
}
|
||||
|
||||
// SendCtx sends the Socket Mode response over a WebSocket connection.
|
||||
// This is usually used for acknowledging requests, but if you need more control
|
||||
// it's normally recommended to use Client.AckCtx() instead of this.
|
||||
func (smc *Client) SendCtx(ctx context.Context, res Response) error {
|
||||
if smc.debug {
|
||||
js, err := json.Marshal(res)
|
||||
|
||||
// Log the error so users of `Send` don't see it entirely disappear as that method
|
||||
// does not return an error and used to panic on failure (with or without debug)
|
||||
smc.Debugf("Scheduling Socket Mode response (error: %v) for envelope ID %s: %s", err, res.EnvelopeID, js)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case smc.socketModeResponses <- &res:
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// receiveMessagesInto attempts to receive an event from the WebSocket connection for Socket Mode.
|
||||
// This will block until a frame is available from the WebSocket.
|
||||
// If the read from the WebSocket results in a fatal error, this function will return non-nil.
|
||||
func (smc *Client) receiveMessagesInto(ctx context.Context, conn *websocket.Conn, sink chan json.RawMessage) error {
|
||||
smc.Debugf("Starting to receive message")
|
||||
defer smc.Debugf("Finished to receive message")
|
||||
|
||||
event := json.RawMessage{}
|
||||
err := conn.ReadJSON(&event)
|
||||
if err != nil {
|
||||
// check if the connection was closed.
|
||||
// This version of the gorilla/websocket package also does a type assertion
|
||||
// on the error, rather than unwrapping it, so we'll do the unwrapping then pass
|
||||
// the unwrapped error
|
||||
var wsErr *websocket.CloseError
|
||||
if errors.As(err, &wsErr) && websocket.IsUnexpectedCloseError(wsErr) {
|
||||
return err
|
||||
}
|
||||
|
||||
if errors.Is(err, io.ErrUnexpectedEOF) {
|
||||
// EOF's don't seem to signify a failed connection so instead we ignore
|
||||
// them here and detect a failed connection upon attempting to send a
|
||||
// 'PING' message
|
||||
|
||||
// Unlike RTM, we don't ping from the our end as there seem to have no client ping.
|
||||
// We just continue to the next loop so that we `smc.disconnected` should be received if
|
||||
// this EOF error was actually due to disconnection.
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// All other errors from ReadJSON come from NextReader, and should
|
||||
// kill the read loop and force a reconnect.
|
||||
// TODO: Unless it's a JSON unmarshal-type error in which case maybe reconnecting isn't needed...
|
||||
smc.sendEvent(ctx, newEvent(EventTypeIncomingError, &slack.IncomingEventError{
|
||||
ErrorObj: err,
|
||||
}))
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
if smc.debug {
|
||||
buf := &bytes.Buffer{}
|
||||
d := json.NewEncoder(buf)
|
||||
d.SetIndent("", " ")
|
||||
if err := d.Encode(event); err != nil {
|
||||
smc.Debugln("Failed encoding decoded json:", err)
|
||||
}
|
||||
reencoded := buf.String()
|
||||
|
||||
smc.Debugln("Incoming WebSocket message:", reencoded)
|
||||
}
|
||||
|
||||
select {
|
||||
case sink <- event:
|
||||
case <-ctx.Done():
|
||||
smc.Debugln("cancelled while attempting to send raw event")
|
||||
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseEvent takes a raw JSON message received from the slack websocket
|
||||
// and handles the encoded event.
|
||||
// returns the our own event that wraps the socket mode request.
|
||||
func (smc *Client) parseEvent(wsMsg json.RawMessage) (*Event, error) {
|
||||
req := &Request{}
|
||||
err := json.Unmarshal(wsMsg, req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unmarshalling WebSocket message: %w", err)
|
||||
}
|
||||
|
||||
var evt Event
|
||||
|
||||
// See below two links for all the available message types.
|
||||
// - https://github.com/slackapi/node-slack-sdk/blob/c3f4d7109062a0356fb765d53794b7b5f6b3b5ae/packages/socket-mode/src/SocketModeClient.ts#L533
|
||||
// - https://api.slack.com/apis/connections/socket-implement
|
||||
switch req.Type {
|
||||
case RequestTypeHello:
|
||||
evt = newEvent(EventTypeHello, nil, req)
|
||||
case RequestTypeEventsAPI:
|
||||
payloadEvent := req.Payload
|
||||
|
||||
eventsAPIEvent, err := slackevents.ParseEvent(payloadEvent, slackevents.OptionNoVerifyToken())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing Events API event: %w", err)
|
||||
}
|
||||
|
||||
evt = newEvent(EventTypeEventsAPI, eventsAPIEvent, req)
|
||||
case RequestTypeDisconnect:
|
||||
// See https://api.slack.com/apis/connections/socket-implement#disconnect
|
||||
|
||||
evt = newEvent(EventTypeDisconnect, nil, req)
|
||||
case RequestTypeSlashCommands:
|
||||
// See https://api.slack.com/apis/connections/socket-implement#command
|
||||
var cmd slack.SlashCommand
|
||||
|
||||
if err := json.Unmarshal(req.Payload, &cmd); err != nil {
|
||||
return nil, fmt.Errorf("parsing slash command: %w", err)
|
||||
}
|
||||
|
||||
evt = newEvent(EventTypeSlashCommand, cmd, req)
|
||||
case RequestTypeInteractive:
|
||||
// See belows:
|
||||
// - https://api.slack.com/apis/connections/socket-implement#button
|
||||
// - https://api.slack.com/apis/connections/socket-implement#home
|
||||
// - https://api.slack.com/apis/connections/socket-implement#modal
|
||||
// - https://api.slack.com/apis/connections/socket-implement#menu
|
||||
|
||||
var callback slack.InteractionCallback
|
||||
|
||||
if err := json.Unmarshal(req.Payload, &callback); err != nil {
|
||||
return nil, fmt.Errorf("parsing interaction callback: %w", err)
|
||||
}
|
||||
|
||||
evt = newEvent(EventTypeInteractive, callback, req)
|
||||
default:
|
||||
return nil, fmt.Errorf("processing WebSocket message: encountered unsupported type %q", req.Type)
|
||||
}
|
||||
|
||||
return &evt, nil
|
||||
}
|
||||
|
||||
// handlePing handles an incoming 'PONG' message which should be in response to
|
||||
// a previously sent 'PING' message. This is then used to compute the
|
||||
// connection's latency.
|
||||
func (smc *Client) handlePing(conn *websocket.Conn, event string) {
|
||||
smc.Debugf("WebSocket ping message received: %s", event)
|
||||
|
||||
// In WebSocket, we need to respond a PING from the server with a PONG with the same payload as the PING.
|
||||
if err := conn.WriteControl(websocket.PongMessage, []byte(event), time.Now().Add(10*time.Second)); err != nil {
|
||||
smc.Debugf("Failed writing WebSocket PONG message: %v", err)
|
||||
}
|
||||
}
|
||||
133
vendor/github.com/slack-go/slack/socketmode/socketmode.go
generated
vendored
Normal file
133
vendor/github.com/slack-go/slack/socketmode/socketmode.go
generated
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
package socketmode
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
|
||||
"github.com/slack-go/slack"
|
||||
)
|
||||
|
||||
// EventType is the type of events that are emitted by scoketmode.Client.
|
||||
// You receive and handle those events from a socketmode.Client.Events channel.
|
||||
// Those event types does not necessarily match 1:1 to those of Slack Events API events.
|
||||
type EventType string
|
||||
|
||||
const (
|
||||
// The following request types are the types of requests sent from Slack via Socket Mode WebSocket connection
|
||||
// and handled internally by the socketmode.Client.
|
||||
// The consumer of socketmode.Client will never see it.
|
||||
|
||||
RequestTypeHello = "hello"
|
||||
RequestTypeEventsAPI = "events_api"
|
||||
RequestTypeDisconnect = "disconnect"
|
||||
RequestTypeSlashCommands = "slash_commands"
|
||||
RequestTypeInteractive = "interactive"
|
||||
|
||||
// The following event types are for events emitted by socketmode.Client itself and
|
||||
// does not originate from Slack.
|
||||
EventTypeConnecting = EventType("connecting")
|
||||
EventTypeInvalidAuth = EventType("invalid_auth")
|
||||
EventTypeConnectionError = EventType("connection_error")
|
||||
EventTypeConnected = EventType("connected")
|
||||
EventTypeIncomingError = EventType("incoming_error")
|
||||
EventTypeErrorWriteFailed = EventType("write_error")
|
||||
EventTypeErrorBadMessage = EventType("error_bad_message")
|
||||
|
||||
//
|
||||
// The following event types are guaranteed to not change unless Slack changes
|
||||
//
|
||||
|
||||
EventTypeHello = EventType("hello")
|
||||
EventTypeDisconnect = EventType("disconnect")
|
||||
EventTypeEventsAPI = EventType("events_api")
|
||||
EventTypeInteractive = EventType("interactive")
|
||||
EventTypeSlashCommand = EventType("slash_commands")
|
||||
|
||||
websocketDefaultTimeout = 10 * time.Second
|
||||
defaultMaxPingInterval = 30 * time.Second
|
||||
)
|
||||
|
||||
// Open calls the "apps.connections.open" endpoint and returns the provided URL and the full Info block.
|
||||
//
|
||||
// To have a fully managed Websocket connection, use `New`, and call `Run()` on it.
|
||||
func (smc *Client) Open() (info *slack.SocketModeConnection, websocketURL string, err error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), websocketDefaultTimeout)
|
||||
defer cancel()
|
||||
|
||||
return smc.StartSocketModeContext(ctx)
|
||||
}
|
||||
|
||||
// OpenContext calls the "apps.connections.open" endpoint and returns the provided URL and the full Info block.
|
||||
//
|
||||
// To have a fully managed Websocket connection, use `New`, and call `Run()` on it.
|
||||
func (smc *Client) OpenContext(ctx context.Context) (info *slack.SocketModeConnection, websocketURL string, err error) {
|
||||
return smc.StartSocketModeContext(ctx)
|
||||
}
|
||||
|
||||
// Option options for the managed Client.
|
||||
type Option func(client *Client)
|
||||
|
||||
// OptionDialer takes a gorilla websocket Dialer and uses it as the
|
||||
// Dialer when opening the websocket for the Socket Mode connection.
|
||||
func OptionDialer(d *websocket.Dialer) Option {
|
||||
return func(smc *Client) {
|
||||
smc.dialer = d
|
||||
}
|
||||
}
|
||||
|
||||
// OptionPingInterval determines how often we expect Slack to deliver WebSocket ping to us.
|
||||
// If no ping is delivered to us within this interval after the last ping, we assumes the WebSocket connection
|
||||
// is dead and needs to be reconnected.
|
||||
func OptionPingInterval(d time.Duration) Option {
|
||||
return func(smc *Client) {
|
||||
smc.maxPingInterval = d
|
||||
}
|
||||
}
|
||||
|
||||
// OptionDebug enable debugging for the client
|
||||
func OptionDebug(b bool) func(*Client) {
|
||||
return func(c *Client) {
|
||||
c.debug = b
|
||||
}
|
||||
}
|
||||
|
||||
// OptionLog set logging for client.
|
||||
func OptionLog(l logger) func(*Client) {
|
||||
return func(c *Client) {
|
||||
c.log = internalLog{logger: l}
|
||||
}
|
||||
}
|
||||
|
||||
// New returns a Socket Mode client which provides a fully managed connection to
|
||||
// Slack's Websocket-based Socket Mode.
|
||||
func New(api *slack.Client, options ...Option) *Client {
|
||||
result := &Client{
|
||||
Client: *api,
|
||||
Events: make(chan Event, 50),
|
||||
socketModeResponses: make(chan *Response, 20),
|
||||
maxPingInterval: defaultMaxPingInterval,
|
||||
log: log.New(os.Stderr, "slack-go/slack/socketmode", log.LstdFlags|log.Lshortfile),
|
||||
}
|
||||
|
||||
for _, opt := range options {
|
||||
opt(result)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// sendEvent safely sends an event into the Clients Events channel
|
||||
// and blocks until buffer space is had, or the context is canceled.
|
||||
// This prevents deadlocking in the event that Events buffer is full,
|
||||
// other goroutines are waiting, and/or timing allows receivers to exit
|
||||
// before all senders are finished.
|
||||
func (smc *Client) sendEvent(ctx context.Context, event Event) {
|
||||
select {
|
||||
case smc.Events <- event:
|
||||
case <-ctx.Done():
|
||||
}
|
||||
}
|
||||
260
vendor/github.com/slack-go/slack/socketmode/socketmode_handler.go
generated
vendored
Normal file
260
vendor/github.com/slack-go/slack/socketmode/socketmode_handler.go
generated
vendored
Normal file
@@ -0,0 +1,260 @@
|
||||
package socketmode
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/slack-go/slack"
|
||||
"github.com/slack-go/slack/slackevents"
|
||||
)
|
||||
|
||||
type SocketmodeHandler struct {
|
||||
Client *Client
|
||||
|
||||
//lvl 1 - the most generic type of event
|
||||
EventMap map[EventType][]SocketmodeHandlerFunc
|
||||
//lvl 2 - Manage event by inner type
|
||||
InteractionEventMap map[slack.InteractionType][]SocketmodeHandlerFunc
|
||||
EventApiMap map[slackevents.EventsAPIType][]SocketmodeHandlerFunc
|
||||
//lvl 3 - the most userfriendly way of managing event
|
||||
InteractionBlockActionEventMap map[string]SocketmodeHandlerFunc
|
||||
SlashCommandMap map[string]SocketmodeHandlerFunc
|
||||
|
||||
Default SocketmodeHandlerFunc
|
||||
}
|
||||
|
||||
// Handler have access to the event and socketmode client
|
||||
type SocketmodeHandlerFunc func(*Event, *Client)
|
||||
|
||||
// Middleware accept SocketmodeHandlerFunc, and return SocketmodeHandlerFunc
|
||||
type SocketmodeMiddlewareFunc func(SocketmodeHandlerFunc) SocketmodeHandlerFunc
|
||||
|
||||
// Initialization constructor for SocketmodeHandler
|
||||
func NewSocketmodeHandler(client *Client) *SocketmodeHandler {
|
||||
eventMap := make(map[EventType][]SocketmodeHandlerFunc)
|
||||
interactionEventMap := make(map[slack.InteractionType][]SocketmodeHandlerFunc)
|
||||
eventApiMap := make(map[slackevents.EventsAPIType][]SocketmodeHandlerFunc)
|
||||
|
||||
interactionBlockActionEventMap := make(map[string]SocketmodeHandlerFunc)
|
||||
slackCommandMap := make(map[string]SocketmodeHandlerFunc)
|
||||
|
||||
return &SocketmodeHandler{
|
||||
Client: client,
|
||||
EventMap: eventMap,
|
||||
EventApiMap: eventApiMap,
|
||||
InteractionEventMap: interactionEventMap,
|
||||
InteractionBlockActionEventMap: interactionBlockActionEventMap,
|
||||
SlashCommandMap: slackCommandMap,
|
||||
Default: func(e *Event, c *Client) {
|
||||
c.log.Printf("Unexpected event type received: %v\n", e.Type)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Register a middleware or handler for an Event from socketmode
|
||||
// This most general entrypoint
|
||||
func (r *SocketmodeHandler) Handle(et EventType, f SocketmodeHandlerFunc) {
|
||||
r.EventMap[et] = append(r.EventMap[et], f)
|
||||
}
|
||||
|
||||
// Register a middleware or handler for an Interaction
|
||||
// There is several types of interactions, decated functions lets you better handle them
|
||||
// See
|
||||
// * HandleInteractionBlockAction
|
||||
// * (Not Implemented) HandleShortcut
|
||||
// * (Not Implemented) HandleView
|
||||
func (r *SocketmodeHandler) HandleInteraction(et slack.InteractionType, f SocketmodeHandlerFunc) {
|
||||
r.InteractionEventMap[et] = append(r.InteractionEventMap[et], f)
|
||||
}
|
||||
|
||||
// Register a middleware or handler for a Block Action referenced by its ActionID
|
||||
func (r *SocketmodeHandler) HandleInteractionBlockAction(actionID string, f SocketmodeHandlerFunc) {
|
||||
if actionID == "" {
|
||||
panic("invalid command cannot be empty")
|
||||
}
|
||||
if f == nil {
|
||||
panic("invalid handler cannot be nil")
|
||||
}
|
||||
if _, exist := r.InteractionBlockActionEventMap[actionID]; exist {
|
||||
panic("multiple registrations for actionID" + actionID)
|
||||
}
|
||||
r.InteractionBlockActionEventMap[actionID] = f
|
||||
}
|
||||
|
||||
// Register a middleware or handler for an Event (from slackevents)
|
||||
func (r *SocketmodeHandler) HandleEvents(et slackevents.EventsAPIType, f SocketmodeHandlerFunc) {
|
||||
r.EventApiMap[et] = append(r.EventApiMap[et], f)
|
||||
}
|
||||
|
||||
// Register a middleware or handler for a Slash Command
|
||||
func (r *SocketmodeHandler) HandleSlashCommand(command string, f SocketmodeHandlerFunc) {
|
||||
if command == "" {
|
||||
panic("invalid command cannot be empty")
|
||||
}
|
||||
if f == nil {
|
||||
panic("invalid handler cannot be nil")
|
||||
}
|
||||
if _, exist := r.SlashCommandMap[command]; exist {
|
||||
panic("multiple registrations for command" + command)
|
||||
}
|
||||
r.SlashCommandMap[command] = f
|
||||
}
|
||||
|
||||
// Register a middleware or handler to use as a last resort
|
||||
func (r *SocketmodeHandler) HandleDefault(f SocketmodeHandlerFunc) {
|
||||
r.Default = f
|
||||
}
|
||||
|
||||
// RunSlackEventLoop receives the event via the socket
|
||||
func (r *SocketmodeHandler) RunEventLoop() error {
|
||||
|
||||
go r.runEventLoop(context.Background())
|
||||
|
||||
return r.Client.Run()
|
||||
}
|
||||
|
||||
func (r *SocketmodeHandler) RunEventLoopContext(ctx context.Context) error {
|
||||
go r.runEventLoop(ctx)
|
||||
|
||||
return r.Client.RunContext(ctx)
|
||||
}
|
||||
|
||||
// Call the dispatcher for each incomming event
|
||||
func (r *SocketmodeHandler) runEventLoop(ctx context.Context) {
|
||||
for {
|
||||
select {
|
||||
case evt, ok := <-r.Client.Events:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
r.dispatcher(evt)
|
||||
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dispatch events to the specialized dispatcher
|
||||
func (r *SocketmodeHandler) dispatcher(evt Event) {
|
||||
var ishandled bool
|
||||
|
||||
// Some eventType can be further decomposed
|
||||
switch evt.Type {
|
||||
case EventTypeInteractive:
|
||||
ishandled = r.interactionDispatcher(&evt)
|
||||
case EventTypeEventsAPI:
|
||||
ishandled = r.eventAPIDispatcher(&evt)
|
||||
case EventTypeSlashCommand:
|
||||
ishandled = r.slashCommandDispatcher(&evt)
|
||||
default:
|
||||
ishandled = r.socketmodeDispatcher(&evt)
|
||||
}
|
||||
|
||||
if !ishandled {
|
||||
go r.Default(&evt, r.Client)
|
||||
}
|
||||
}
|
||||
|
||||
// Dispatch socketmode events to the registered middleware
|
||||
func (r *SocketmodeHandler) socketmodeDispatcher(evt *Event) bool {
|
||||
if handlers, ok := r.EventMap[evt.Type]; ok {
|
||||
// If we registered an event
|
||||
for _, f := range handlers {
|
||||
go f(evt, r.Client)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Dispatch interactions to the registered middleware
|
||||
func (r *SocketmodeHandler) interactionDispatcher(evt *Event) bool {
|
||||
var ishandled bool = false
|
||||
|
||||
interaction, ok := evt.Data.(slack.InteractionCallback)
|
||||
if !ok {
|
||||
r.Client.log.Printf("Ignored %+v\n", evt)
|
||||
return false
|
||||
}
|
||||
|
||||
// Level 1 - socketmode EventType
|
||||
ishandled = r.socketmodeDispatcher(evt)
|
||||
|
||||
// Level 2 - interaction EventType
|
||||
if handlers, ok := r.InteractionEventMap[interaction.Type]; ok {
|
||||
// If we registered an event
|
||||
for _, f := range handlers {
|
||||
go f(evt, r.Client)
|
||||
}
|
||||
|
||||
ishandled = true
|
||||
}
|
||||
|
||||
// Level 3 - interaction with actionID
|
||||
blockActions := interaction.ActionCallback.BlockActions
|
||||
// outmoded approach won`t be implemented
|
||||
// attachments_actions := interaction.ActionCallback.AttachmentActions
|
||||
|
||||
for _, action := range blockActions {
|
||||
if handler, ok := r.InteractionBlockActionEventMap[action.ActionID]; ok {
|
||||
|
||||
go handler(evt, r.Client)
|
||||
|
||||
ishandled = true
|
||||
}
|
||||
}
|
||||
return ishandled
|
||||
}
|
||||
|
||||
// Dispatch eventAPI events to the registered middleware
|
||||
func (r *SocketmodeHandler) eventAPIDispatcher(evt *Event) bool {
|
||||
var ishandled bool = false
|
||||
eventsAPIEvent, ok := evt.Data.(slackevents.EventsAPIEvent)
|
||||
if !ok {
|
||||
r.Client.log.Printf("Ignored %+v\n", evt)
|
||||
return false
|
||||
}
|
||||
|
||||
innerEventType := slackevents.EventsAPIType(eventsAPIEvent.InnerEvent.Type)
|
||||
|
||||
// Level 1 - socketmode EventType
|
||||
ishandled = r.socketmodeDispatcher(evt)
|
||||
|
||||
// Level 2 - EventAPI EventType
|
||||
if handlers, ok := r.EventApiMap[innerEventType]; ok {
|
||||
// If we registered an event
|
||||
for _, f := range handlers {
|
||||
go f(evt, r.Client)
|
||||
}
|
||||
|
||||
ishandled = true
|
||||
}
|
||||
|
||||
return ishandled
|
||||
}
|
||||
|
||||
// Dispatch SlashCommands events to the registered middleware
|
||||
func (r *SocketmodeHandler) slashCommandDispatcher(evt *Event) bool {
|
||||
var ishandled bool = false
|
||||
slashCommandEvent, ok := evt.Data.(slack.SlashCommand)
|
||||
if !ok {
|
||||
r.Client.log.Printf("Ignored %+v\n", evt)
|
||||
return false
|
||||
}
|
||||
|
||||
// Level 1 - socketmode EventType
|
||||
ishandled = r.socketmodeDispatcher(evt)
|
||||
|
||||
// Level 2 - SlackCommand by name
|
||||
if handler, ok := r.SlashCommandMap[slashCommandEvent.Command]; ok {
|
||||
|
||||
go handler(evt, r.Client)
|
||||
|
||||
ishandled = true
|
||||
}
|
||||
|
||||
return ishandled
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user