- やってみたら結構簡単にできたのでメモ。Elixir-Slackを使いました。このページに書いてある通りに進めます。
- 2015/09/28 大幅にコード書き換えたので記事も合わせて修正しておきます。
準備
- Slackは事前にbotを作ってAPIキーを用意してください。
- とりあえずElixirのアプリケーションを作成。今回はElixirなんでFFっぽい名前でマジックボットで作ってます。
> mix new magic_bot
- cd してmix.exsのapplicationとdepsを編集。終わったらmix deps.getしとく。
mix.exs
def application do
[applications: [:logger, :slack],
mod: {MagicBot, []}]
end
defp deps do
[
{:websocket_client, github: "jeremyong/websocket_client"},
{:slack, "~> 0.2.0"}
]
end
- configにAPIキーの情報を追加
config/config.exs
use Mix.Config
config :MagicBot,
api_key: "BotのAPIキー"
スクリプト作成
Slackのコネクションを管理するモジュール
- slackのコネクションをstateでもち回し続けるプロセス。メッセージを受け取ったらBotAction.Supervisorの下にメッセージに対応したスクリプトを実行するプロセスを新しく作成する。
lib/magic_bot/bot.ex
defmodule MagicBot.Bot do
use Slack
def handle_connect(slack, state) do
# 接続に成功したら呼ばれるコールバック
IO.puts "Connected as #{slack.me.name}"
{:ok, state}
end
def handle_message(message = %{type: "message", text: _}, slack, state) do
# メッセージを受け取ったら呼ばれるコールバック
trigger = String.split(message.text, ~r{ | })
case String.starts_with?(message.text, "<@#{slack.me.id}>: ") do
# botにメンション飛ばしたらrespondでプロセス作成
true -> BotAction.Supervisor.start_action(state[:sup_action], :respond, Enum.fetch!(trigger, 1), message, slack)
# それ以外はhearでプロセス作成
false -> BotAction.Supervisor.start_action(state[:sup_action], :hear, hd(trigger), message, slack)
end
{:ok, state}
end
def handle_message(_message, _slack, state) do
{:ok, state}
end
end
Slackから受け取ったbotのスクリプトのプロセスを管理するsupervisor
- Slackのプロセスから
start_action/1
が呼ばれ、このTask.Supervisor配下に新しいプロセスを作成する。 - Task.Supervisorの子プロセスの戦略は
simple_one_for_one
になり、動的に追加できる。 - クラッシュ時は子プロセスは再起動されない
lib/bot_action/supervisor.ex
defmodule BotAction.Supervisor do
def start_link(opts \\ []) do
Task.Supervisor.start_link(opts)
end
def start_action(supervisor, command, trigger, message, slack) do
Task.Supervisor.start_child(supervisor, fn ->
case command do
:respond -> BotAction.Action.respond(trigger, message, slack)
:hear -> BotAction.Action.hear(trigger, message, slack)
end
end)
end
end
実際にBotに何かさせたいスクリプトを書く部分
- hubotのhearとrespondっぽい感じで実装してます。@bot名 hogeで来たらrespond/3をそれ以外はhear/3が動くようになっています。
- パターンマッチで合うものがあれば動いて、なければ何もせずに終わります。
lib/bot_action/action.ex
defmodule BotAction.Action do
def hear("hear?", message, slack) do
send_message("I can hear", message.channel, slack)
end
# Don't remove. This is default pattern.
def hear(_, _, _) do
end
def respond("respond?", message, slack) do
send_message("I can respond", message.channel, slack)
end
# Don't remove. This is default pattern.
def respond(_, _, _) do
end
end
全体を管理するSupervisor
lib/magic_bot/bot.ex
defmodule MagicBot.Supervisor do
use Supervisor
def start_link(opts \\ []) do
Supervisor.start_link(__MODULE__, :ok, opts)
end
@bot_name MagicBot.Bot
@action_sup_name BotAction.Supervisor
def init(:ok) do
# API キーを取得。環境変数から取れなければconfigから取得。
api_key = case System.get_env("MAGICBOT_API_KEY") do
nil -> Application.get_env(:MagicBot, :api_key)
s -> s
end
# アプリケーションのSupervisorで管理する子プロセスを作成
children = [
supervisor(BotAction.Supervisor, [[name: @action_sup_name]]),
worker(MagicBot.Bot, [api_key, [name: @bot_name, sup_action: @action_sup_name]])
]
# 戦略は `one_for_one`でSlackとアクションのsupervisorを起動
supervise(children, strategy: :one_for_one)
end
end
最後に起動用のモジュール
-
mix.exs
で指定したモジュール。MagicBotのSupervisorを開始する。
lib/magic_bot.ex
defmodule MagicBot do
use Application
def start(_type, _args) do
MagicBot.Supervisor.start_link
end
end
起動
-no-haltオプションを付けることでプロセスが回り続けるらしい。
mix run --no-halt
話しかけてみる
何かbotの動作を追加してみる
lgtmとつぶやいたら lgtm.in から lgtm用画像を取ってくるScriptを追加してみます。
lib/bot_action/action.ex
require HTTPoison
require Floki
def hear("lgtm", message, slack) do
HTTPoison.start
case URI.encode("http://www.lgtm.in/g") |> HTTPoison.get do
{:ok, %HTTPoison.Response{status_code: 200, body: body}} -> body
|> Floki.find("#imageUrl")
|> Floki.attribute("value")
|> hd
|> send_message(message.channel, slack)
{_, _} -> nil
end
end
動かしてみる
Herokuにデプロイしてプロセスを永続化する
- 今はMacの中で動かしていますが、これだとMacを閉じたらBotは眠りについてしまうのでHerokuにデプロイしてElixirのプロセスを回してみます。
- GitHubと連携する体で進めるので、コードはGitHubにあげます。
- Herokuはあまり詳しくないので間違っていたら指摘ください。
Herokuにアプリケーションを作成
- Herokuのビルドパックの一覧にheroku-buildpack-elixirがあるのでこれを使います。
> heroku create magic-bot --buildpack "https://github.com/HashNuke/heroku-buildpack-elixir.git"
Herokuにデプロイする
- buildpackの説明に従って以下のファイルを作成
- データベースはいまのとこ使わないのでコメントアウト
elixir_buildpack.config
# Erlang version
erlang_version=17.5
# Elixir version
elixir_version=1.0.4
# Always rebuild from scratch on every deploy?
always_rebuild=false
# Export heroku config vars
# config_vars_to_export=(DATABASE_URL)
- Heroku用のBootファイル
Procfile
botworker: mix run --no-halt
- Heroku側の設定でMAGICBOT_API_KEYにConfigに書いているキーを設定。configはignoreに追加してcommit対象外にしとく。
- ここまでできたらCommitしてGithubにpush
- あとはHeroku側でGithubとの接続を有効にしてManualDeployして完了です。
- Automatic deploysを有効にすれば、以降はBotを編集してGithubにPushすれば自動でBotが更新されるようになります。
今回のコード