Packages

A Slack Web & Real Time Messaging API client.

Retired package: Renamed - 1.0.0-alpha.0

Current section

Files

Jump to
slack_kit lib slack bot.ex
Raw

lib/slack/bot.ex

defmodule Slack.Bot do
@moduledoc """
This module is used to spawn bots and is used to manage the connection to Slack
while delegating events to the specified bot module.
"""
require Logger
@behaviour Slack.WebSocketClient
@doc """
Connects to Slack and delegates events to `bot_handler`.
## Options
* `keepalive` - How long to wait for the connection to respond before the client kills the connection.
* `name` - registers a name for the process with the given atom
## Examples
{:ok, pid} = Slack.Bot.start_link(MyBot, [1,2,3], "abc-123", %{name: :slack_bot})
:sys.get_state(:slack_bot)
"""
def start_link(bot_handler, initial_state, token, options \\ %{}) do
options =
Map.merge(
%{
client: Slack.WebSocketClient,
keepalive: 10_000,
name: nil
},
Map.new(options)
)
rtm_module = Application.get_env(:slack, :rtm_module, Slack.Rtm)
case rtm_module.start(token) do
{:ok, rtm} ->
state = %{
bot_handler: bot_handler,
rtm: rtm,
client: options.client,
token: token,
initial_state: initial_state
}
{:ok, pid} =
options.client.start_link(state.rtm.url, __MODULE__, state,
keepalive: options.keepalive
)
if options.name != nil do
Process.register(pid, options.name)
end
{:ok, pid}
{:error, %Req.TransportError{reason: :timeout}} ->
{:error, "Timed out while connecting to the Slack RTM API"}
{:error, %Req.TransportError{reason: :nxdomain}} ->
{:error, "Could not connect to the Slack RTM API"}
{:error, %Slack.JsonDecodeError{string: "You are sending too many requests. Please relax."}} ->
{:error, "Sent too many connection requests at once to the Slack RTM API."}
{:error, error} ->
{:error, error}
end
end
@doc false
@deprecated """
`rtm.start` is replaced with `rtm.connect` and will no longer receive bots, channels, groups, users, or IMs.
In future versions these will no longer be provided on initialization.
"""
def init(%{
bot_handler: bot_handler,
rtm: rtm,
client: client,
token: token,
initial_state: initial_state
}) do
slack = %Slack.State{
process: self(),
client: client,
token: token,
me: rtm.self,
team: rtm.team
}
{:reconnect, %{slack: slack, bot_handler: bot_handler, process_state: initial_state}}
end
@doc false
def onconnect(
_websocket_request,
%{slack: slack, process_state: process_state, bot_handler: bot_handler} = state
) do
{:ok, new_process_state} = bot_handler.handle_connect(slack, process_state)
{:ok, %{state | process_state: new_process_state}}
end
@doc false
def ondisconnect({:error, :keepalive_timeout}, state) do
{:reconnect, state}
end
def ondisconnect(
reason,
%{slack: slack, process_state: process_state, bot_handler: bot_handler} = state
) do
try do
bot_handler.handle_close(reason, slack, process_state)
{:close, reason, state}
rescue
e -> log_and_reraise(e, __STACKTRACE__)
end
end
@doc false
def websocket_info(
message,
_connection,
%{slack: slack, process_state: process_state, bot_handler: bot_handler} = state
) do
try do
{:ok, new_process_state} = bot_handler.handle_info(message, slack, process_state)
{:ok, %{state | process_state: new_process_state}}
rescue
e -> log_and_reraise(e, __STACKTRACE__)
end
end
@doc false
def websocket_terminate(_reason, _conn, _state), do: :ok
@doc false
def websocket_handle(
{:text, message},
_conn,
%{slack: slack, process_state: process_state, bot_handler: bot_handler} = state
) do
message = prepare_message(message)
updated_slack =
if Map.has_key?(message, :type) do
Slack.State.update(message, slack)
else
slack
end
new_process_state =
if Map.has_key?(message, :type) do
try do
{:ok, new_process_state} = bot_handler.handle_event(message, slack, process_state)
new_process_state
rescue
e -> log_and_reraise(e, __STACKTRACE__)
end
else
process_state
end
{:ok, %{state | slack: updated_slack, process_state: new_process_state}}
end
def websocket_handle(_, _conn, state), do: {:ok, state}
defp prepare_message(binstring) do
binstring
|> :binary.split(<<0>>)
|> List.first()
|> JSON.decode!()
|> Slack.JSON.atomize_keys()
end
defp log_and_reraise(e, stacktrace) do
Logger.error(Exception.format(:error, e, stacktrace))
reraise e, stacktrace
end
end