Clojure で Slack の Bot を作ろう


今回は拙作の jubot というフレームワークを使って Clojure で Bot を作り、それを Heroku へデプロイ、Slack と連携させるまでの手順を紹介したいと思います。

jubot とは

Chatbot framework in Clojure.
https://github.com/liquidz/jubot

Clojure で Chatbot を作るためのフレームワークです。
いかに私が楽して開発できるかを考えて作っているので、比較的簡単に Bot を実装することができます。
ver 0.0.1 では Slack との連携、Redis を使ったデータ永続化、タスクのスケジュール実行をサポートしていますが、今回は Slack との連携にフォーカスを絞って説明します。

対象としている環境

  • Clojure の実行環境があればどこでも
    • Windows は未確認

なお Clojure の開発に必要な JDK, Leiningen のインストールは済んでいるものとして話を進めます。

開発の流れ

基本的な開発の流れは以下の通りです。
テンプレート作成は最初にするだけなので、2-3 をグルグル回す形になります。

  1. テンプレートを作成する
  2. ハンドラーを作成する
  3. 動作確認

テンプレートを作る

まずは Bot のテンプレートを作りましょう。jubot では Leiningen のテンプレートを用意しているので lein コマンドで一発です。簡単ですね。

$ lein new jubot mybot
$ cd mybot

ハンドラーを作成する

jubot ではユーザーの入力に対して Bot が反応する内容をまとめた処理をハンドラーといいます。
1 から Bot を作ろうと思うとユーザーからの入力を受け取る処理、レスポンスを Slack なり何なりのサービス側へ返す処理も実装しなければいけませんが、そういった面倒なことは全て jubot がやってくれます。
開発者はただ「 "foo" という入力がきたら "bar" を返す」という処理だけを実装するだけです。

作成したテンプレートにはサンプルのハンドラーが含まれているので、今回はそれを見てみましょう。

  • src/mybot/sample/pingpong.clj
(ns myjubot.sample.pingpong)

(defn ping-handler
  "jubot ping - reply with 'pong'"
  [{text :text}]
  (if (= text "ping") "pong"))

これは "ping" という入力に対して "pong" と返すハンドラーです。

戻り値として文字列を返せば、それが入力へのレスポンスになります。
文字列以外を返した場合、jubot は何も返しません。

また見てわかる通り jubot のハンドラーは単なる関数で、DSLは使いません。
これには2つの大きな利点があります。

  1. 学習コストが低い
  2. テストが書きやすい

上記サンプルのテストは test/mybot/sample/pingpong_test.clj にあります。
こちらも clojure.test を使ったテストになっているので、テストの書き方を新たに学ぶ必要はありません。

(ns mybot.sample.pingpong-test
  (:require
    [mybot.sample.pingpong :refer :all]
    [clojure.test :refer :all]))

(deftest test-ping-handler
  (are [x y] (= x (ping-handler y))
    nil    {}
    "pong" {:text "ping"}))

ハンドラーの命名規則

学習コストが低いといっても全くルールがないわけではありません。
ハンドラーの関数には命名規則があり、必ず -handler で終わる関数名にする必要があります。

jubot では lein new jubot mybot でテンプレートを作成した場合、
mybot という名前空間配下から -handler で終わるパブリックな関数を自動的に収集して Bot として動かします。
なので命名規則に従っていないハンドラーは無視されてしまうので注意してください。

動作確認

では実際にサンプルのハンドラーを動かしてみましょう。
ローカルでの動作確認には REPL が利用できます。

$ lein repl
user=> (start)
;; start repl adapter. bot name is jubot
... 省略 ...

REPL上で動作確認する際のデフォルトの bot の名前はメッセージに出てくる通り "jubot" になります。
では次にサンプルで紹介した pingpong を試してみましょう。

user=> (in "jubot ping")

サンプルのように "pong" が返ってきましたか?
このように REPL 上では in 関数を使って動作確認をすることができます。

また定義しているハンドラーのヘルプは (in "jubot help") で見れます。

修正したソースの反映

動作確認は REPL 上で in 関数を使えばいいことがわかりました。
ではここで src/mybot/sample/pingpong.clj を以下のように修正したとしましょう。

(defn ping-handler
  "jubot ping - reply with 'ポンッ!!!'"
  [{text :text}]
  (if (= text "ping") "ポンッ!!!"))

この修正の確認をするのに REPL の再起動する必要はありません。
restart 関数を使うことで修正内容を再読み込みできます。

user=> (restart)
; ... 省略 ...
user=> (in "jubot ping")

Clojure での開発は基本的に REPL を立ち上げた状態で行うことがほとんどだと思います。
jubot での開発も同じスタイルで開発を進めることができます。

Heroku へのデプロイ

ここまではローカルでの開発と動作確認の方法を説明しました。
このままでは Slack と連携ができないので、Heroku へデプロイしてみましょう。

jubot のテンプレートには Heroku で動かすとき用に Procfile が用意されているのでデプロイは比較的簡単です。
なおデフォルトで Bot の名前は "jubot" になっているので、必要に応じて Procfile 内の --name の値を変更してください。

  • Procfile
web: java $JVM_OPTS -cp target/mybot-standalone.jar clojure.main -m mybot.core --name jubot --adapter slack --brain redis

Heroku の操作には Heroku Toolbelt を使うので、インストールしていない場合にはインストールしてください。

  • コミット
$ git init
$ git add .
$ git commit -am "first commit"
  • デプロイ
$ heroku login
$ heroku keys:add /path/to/public/key
$ heroku apps:create <アプリ名>
$ git push heroku master

プッシュ後に "https://<アプリ名>.herokuapp.com/" へアクセスして、
以下のようなメッセージが出てくることが確認できればデプロイ完了です。

this is jubot slack adapter. bot's name is "jubot".

余談: Heroku での asleep ステータス回避

余談ですが以下の環境変数を設定しておくと、デプロイしたアプリが asleep になりません。
Bot のアプリが asleep になってしまうと何かと不便なので、Heroku にデプロイする際には設定しておくといいでしょう。

$ heroku config:add AWAKE_URL="https://<アプリ名>.herokuapp.com/"

Slack と連携させる

Slack と連携させるには Integrations で Outgoing WebHooks を設定する必要があります。
(WebSocket を使った連携は ver 0.0.1 では未対応です)

Outgoing WebHooks の設定内容は以下の通りです。

項目
Channel 任意のチャンネルを選択
Trigger Word(s) 空のまま
URL(s) https://<アプリ名>.herokuapp.com/

次に Outgoing WebHooks の Token を heroku 上で動いている Bot に登録しましょう。

$ heroku config:add SLACK_OUTGOING_TOKEN=<Outgoing WebHooks の Token>

これで連携完了です!
試しに Slack 上で "jubot ping" と打ってみて、"pong" が返ることを確認してみてください。

最後に

今回は jubot を使った Bot 作成のフローをわかりやすくするために最低限の説明にとどめました。

今回の内容に含まれていない jubot の機能としては大きく以下の2点があります。

  • jubot.brain を使ったデータの永続化
  • jubot.scheduler を使ったタスクのスケジュール実行

これらの詳細については github 上の README にまとめているので、この記事で興味をもっていただけた方は是非参照してみてください。

また jubot はまだ ver 0.0.1 が出たばかりで機能の不足、バグが多々あります。
問題点などありましたら Issue での報告、プルリクエストに限らず @uochan へのメンションでもいいのでご連絡いただけると助かります!