hubotでSlack botを作っていたときに思っていたことがある. この正規表現はかなりめんどくさい.Elixirのパターンマッチで書いたらもっと楽になるのではないだろうか,と(そう簡単な話でもなかったのだが).
というわけでElixirでslack botを作ってみようと思ったのだが,そんなに整備された道がなかったのでメモ.
どのライブラリを使うか
ライブラリを使わないという手もあるのだが,一応awesome-elixirを見るといくつか使えそうなやつのがあるので使ってみる.
ここで,Qiita記事とかを検索してしまったのだが,
Slothはエラーが出て全然使えなかった.
というか,Slack側が少しbot周りを整理しており,以前とbotの作り方が変わっていたりするかもしれない.
elixirの流行り廃りなのか,たとえばslackerあたりは,2016年で更新が止まっている. こういうライブラリを使うと後々苦労するので,たとえ日本語での情報が多かったとしても,枯れているライブラリを使うべきではない.
そんなわけでElixir-Slackを使ってみた.
こいつは2018年現在でも活発に開発されている.
自分でプロセスを作らないといけない
ただ,Elixir-SlackはSlothのように,「ここにメッセージのハンドリングを書けば動く!」というほど簡単にはできていない. READMEには簡単そうに書いてあるけど…….
そもそもElixir-Slackは,Slackとの認証,websocket接続あたりまでを請け負うものなので,それを動かすプロセスは自分で作る必要がある.
というわけで,Supervisorの出番だ.
defmodule SampleBot do use Application def start(_type, _args) do import Supervisor.Spec, warn: false children = [ worker(SampleBot.Bot, [[]]), ] opts = [strategy: :one_for_one, name: SampleBot.Supervisor] Supervisor.start_link(children, opts) end end
で,子プロセスを作る.
defmodule SampleBot.Bot do use Slack def start_link(initial_state) do Slack.Bot.start_link(__MODULE__, initial_state, Application.get_env(:release_bot, :slack_token)) end def handle_connect(slack, state) do IO.puts "Connected as #{slack.me.id}" {:ok, state} end def handle_event(message = %{type: "message", text: text}, slack, state) do send_message('pong', message.channel, slack) {:ok, state} end def handle_event(_, _, state), do: {:ok, state} def handle_info({:message, text, channel}, slack, state) do IO.puts "Sending your message, captain!" send_message(text, channel, slack) {:ok, state} end def handle_info(_, _, state), do: {:ok, state} end
こいつを--no-halt
フラグをつけて実行すればslack botが起動する.
とりあえず,すべてのメッセージにpong
を返すだけ.
$ mix run --no-halt
パターンマッチを使うには
さて,せっかくelixirを使っているんだからメッセージのハンドリングはパターンマッチを使いたい.
しかし,いくらelixirといえど,単なるStringに対してできることといえば,正規表現マッチくらいなものだ. これだとhubotのときとあまり変わらなく,ありがたみが少ない.
だが,これはもとが単なる文字列だからだ.
文字列を何かしらのデータ構造に落とし込めばいいのでは?
つまり,たとえば String.split
とかして,単語ごとの文字列に分割するというのはどうだろう.
hello world
であるなら,
"hello world" |> String.split(" ") # => ["hello", "world"]
とすれば良いのでは?
というわけで,リプライ時のみ反応する形にしてみる.
def handle_event(message = %{type: "message", text: text}, slack, state) do text |> String.split(" ") |> SampleBot.Handler.handle_message(message.channel, slack, "<@#{slack.me.id}>") {:ok, state} end
defmodule SampleBot.Handler do use Slack def handle_message([user_id, "ping"] = _text, channel, slack, user_id) do send_message("pong", channel, slack) end def handle_message([user_id | mes] = _text, channel, slack, user_id) do send_message("Hello world", channel, slack) end def handle_message(_, _, _, _) do :ok end end
これでパターンマッチをそれなりに使った形でbotが作れるぞー.
作ったソースはこちら.