SlackSocketModeBot
This is a simple Ruby wrapper for the Slack's Socket Mode API. It allows you to write a Slack bot without exposing a public HTTP endpoint.
Usage
First, set up your Slack app for Socket Mode by reading the official document.
Then, run the following script.
# simple echo bot
require "slack_socket_mode_bot"
require "logger"
# Slack's Bot User OAuth Token
# You can create this token with: https://api.slack.com/apps/ - "OAuth & Permissions" - "OAuth Tokens for Your Workspace"
SLACK_BOT_TOKEN = ENV.fetch("SLACK_BOT_TOKEN") # "xoxb-...", or "test" for test mode
# Slack's App-Level Token
# You can create this token with: https://api.slack.com/apps/ - "Basic Information" - "App-Level Tokens"
SLACK_APP_TOKEN = ENV.fetch("SLACK_APP_TOKEN") # "xapp-..."
logger = Logger.new(STDOUT, level: Logger::Severity::INFO)
bot = SlackSocketModeBot.new(token: SLACK_BOT_TOKEN, app_token: SLACK_APP_TOKEN, logger: logger) do |data|
# Event handler. A sample data is
#
# {
# "type": "events_api",
# "envelope_id": "...",
# "accepts_response_payload": false,
# "payload": {
# "type": "event_callback",
# "event": {
# "type": "app_mention",
# "text": "hello",
# ...
# },
# ...
# }
# }
#
# See https://api.slack.com/apis/socket-mode#events in detail
if data[:type] == "events_api" && data[:payload][:event][:type] == "app_mention"
event = data[:payload][:event]
text = event[:text]
echo_text = "echo:" + text
bot.call("chat.postMessage", { channel: event[:channel], text: echo_text })
end
rescue Exception
puts $!.
end
# Start the communication
bot.run unless bot.test_mode?
# Test
if bot.test_mode?
# Stub chat.postMessage
posted = []
bot.test_mode.stub_web_api("chat.postMessage") {|data| posted << data }
# Simulate receiving an event from Slack
data = {
type: "events_api",
payload: {
event: {
type: "app_mention",
text: "hello",
channel: "C123",
}
}
}
bot.test_mode.simulate_envelope(data)
# OK if chat.postMessage was sent
ok = posted == [{ channel: "C123", text: "echo:hello" }]
puts "test: #{ ok ? "OK" : "NG" }"
end
$ SLACK_BOT_TOKEN=test SLACK_APP_TOKEN=test ruby examples/echo_bot.rb
test: OK
$ SLACK_BOT_TOKEN=xoxb-... SLACK_APP_TOKEN=xapp-... ruby examples/echo_bot.rb
I, [20XX-XX-XXTXX:XX:XX.XXXXXX #XXXXXX] INFO -- : [ws:2560] websocket open
I, [20XX-XX-XXTXX:XX:XX.XXXXXX #XXXXXX] INFO -- : [ws:2600] websocket open
I, [20XX-XX-XXTXX:XX:XX.XXXXXX #XXXXXX] INFO -- : [ws:2640] websocket open
I, [20XX-XX-XXTXX:XX:XX.XXXXXX #XXXXXX] INFO -- : [ws:2680] websocket open
I, [20XX-XX-XXTXX:XX:XX.XXXXXX #XXXXXX] INFO -- : [ws:2560] hello (active connections: 4)
I, [20XX-XX-XXTXX:XX:XX.XXXXXX #XXXXXX] INFO -- : [ws:2600] hello (active connections: 4)
I, [20XX-XX-XXTXX:XX:XX.XXXXXX #XXXXXX] INFO -- : [ws:2640] hello (active connections: 4)
I, [20XX-XX-XXTXX:XX:XX.XXXXXX #XXXXXX] INFO -- : [ws:2680] hello (active connections: 4)
I, [20XX-XX-XXTXX:XX:XX.XXXXXX #XXXXXX] INFO -- : [ws:2680] events_api app_mention Ev08H3RABCDE
I, [20XX-XX-XXTXX:XX:XX.XXXXXX #XXXXXX] INFO -- : [ws:2680] events_api app_mention Ev08H3RFGHIJ
I, [20XX-XX-XXTXX:XX:XX.XXXXXX #XXXXXX] INFO -- : [ws:2680] events_api app_mention Ev08H3RKLMNO
...
API
SlackSocketModeBot.new(token:, app_token:, logger:)
Connects to Slack with Socket Mode.
token: Slack's Bot User OAuth token (starting withxoxb-), or"test"to build a test-mode bot (see below)app_token: Slack's App-Level token (starting withxapp-)logger: A Logger instance (optional)- block: Handles events received from Slack
Note: The block must return as soon as possible. Otherwise, the Slack server will re-send the event. If you want to do a time-consuming process, it is recommended that you do it in a sub thread.
SlackSocketModeBot#call(method, data, token:)
Calls Slack's Web API, such as chat.postMessage.
method: API name (such as"chat.postMessage")data: Arguments
This method returns the response as a JSON data.
SlackSocketModeBot#run
Starts the main loop of communication with Slack. This method does not return.
On an unrecoverable error (for example, losing every connection and being unable to reopen any), this method raises an exception instead of silently continuing in a degraded state. Running your bot under a process supervisor such as systemd is recommended so that it is restarted when this happens.
SlackSocketModeBot#step
Proceeds with the communication one step.
This method returns an array of IO waiting to be readable and an array of IO waiting to be writable.
They are supposed to be passed to IO.select.
Typically, this method should be used as follows.
while true
read_ios, write_ios = app.step
IO.select(read_ios, write_ios)
end
This method allows you to manage the main loop yourself.
If you don't need it, you can just use SlackSocketModeBot#run.
Test mode
Passing token: "test" (SlackSocketModeBot.new(token: "test")) puts the bot in test mode.
In this mode, the bot never connects to Slack.
Instead of opening a socket, you feed it events and stub the Web API yourself.
So you can test your handler offline.
See examples/test_echo_bot.rb for complete examples.
SlackSocketModeBot#test_mode?
Returns whether the bot is in test mode, i.e. whether it was built with token: "test".
SlackSocketModeBot#test_mode
Returns the bot's SlackSocketModeBot::TestMode instance in test mode, or nil otherwise.
All of the methods below are called on it (e.g. bot.test_mode.simulate_envelope(...)).
SlackSocketModeBot::TestMode#stub_web_api(method, &block)
Stubs a Web API method's response.
bot.call(method, data) then runs the block instead of making a real HTTP request.
An unstubbed call returns { ok: true }.
When $VERBOSE is set, it also prints a warning.
SlackSocketModeBot::TestMode#simulate_envelope(envelope)
Delivers an envelope to the bot as if it had arrived over the socket from Slack. You can use it to run and test your event handler synchronously.
envelope is a Hash (string or symbol keys) shaped like the JSON Slack sends.
:envelope_id is filled in automatically if omitted.
SlackSocketModeBot::TestMode#reset
Clears all registered stubs and the event-deduplication state.
Call it between test cases (for example, setup or before hooks).