Edited at

ElixirでSlackのbotを作ってHerokuで動かしてみる

More than 3 years have passed since last update.


  • やってみたら結構簡単にできたのでメモ。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


話しかけてみる

スクリーンショット 2015-08-20 23.37.04.png


何か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


動かしてみる

スクリーンショット 2015-08-21 19.16.39.png


Herokuにデプロイしてプロセスを永続化する


  • 今はMacの中で動かしていますが、これだとMacを閉じたらBotは眠りについてしまうのでHerokuにデプロイしてElixirのプロセスを回してみます。

  • GitHubと連携する体で進めるので、コードはGitHubにあげます。

  • Herokuはあまり詳しくないので間違っていたら指摘ください。


Herokuにアプリケーションを作成

> 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が更新されるようになります。


今回のコード

https://github.com/rei-m/magic_bot