GitHub社謹製! bot開発・実行フレームワーク「Hubot」

第4回 Hubotのスクリプトを書いてみる

この記事を読むのに必要な時間:およそ 5 分

今回は,Hubotのスクリプトが動く仕組みについて説明し,基本的な機能であるチャットでの受け答えを実装する方法を説明した後に,その他の機能について紹介します。

スクリプトの基本

Hubotがスクリプトを読み込み実行する仕組みを説明するために,helloと挨拶するとHubotがhiと返事する単純なスクリプトのサンプルを示します。

hello.coffee

module.exports = (robot) ->
  robot.hear /hello/, (msg) ->
    msg.reply 'hi'

このサンプルコードの一番外側を見ると,module.exportsに関数を代入しています。このmodule.exportsは,Node.jsでモジュールを作るための仕組みです。つまり,Hubotのスクリプトとは,引数を1つとる1つの関数を提供するNode.jsのモジュールということになります。

サンプルコードでは,関数の中で引数robothearメソッドを呼び出してコールバック関数を登録しています。このrobotは,Hubotの本体とも言えるRobotクラスのインスタンスです。

このように,基本的なHubotのスクリプトでは,Robotオブジェクトのメソッドを通じてコールバック関数を登録することで,「チャット上で誰かが特定のキーワードを発言したとき」「チャットルームに誰かが新たに参加したとき」など様々な状況ごとにHubotの振る舞いを定義していきます。

Node.jsのモジュールの詳しい仕組みは,Node.jsのドキュメントをご確認ください。

スクリプトの読み込み

では,作成したモジュール,つまりHubotのスクリプトはいつどのように読み込まれ実行されるのでしょうか。

Hubotが起動してAdapterがチャットツールと接続したとき,Hubotは./scripts./src/scriptshubot-scripts.json, external-scripts.jsonHUBOT_SCRIPTSで指定したpathからNode.jsのモジュールを探し,requireすると同時にmodule.exportsに代入された関数をRobotオブジェクトを引数にして呼び出します。

Hubot(Robotオブジェクト)が特定のスクリプトファイルを読み込むコードの例を次に抜粋します。

211  loadFile: (path, file) ->
212    ext  = Path.extname file
213    full = Path.join path, Path.basename(file, ext)
214    if require.extensions[ext]
215      try
216        require(full) @
217        @parseHelp Path.join(path, file)
218      catch error
219        @logger.error "Unable to load #{full}: #{error.stack}"
220        process.exit(1)

引用元:執筆時点のRobotクラスのloadFileメソッド

216行目で,読み込み対象のモジュールをrequireしつつexportされた関数を自分自身を引数として呼び出していることがわかります。

まとめ

スクリプトの登録から呼び出しまでの流れを図1に示します。

図1 イベントリスナの登録と実行

図1 イベントリスナの登録と実行

まとめると,まずHubotが起動してAdapterがチャットツールに接続したときに作成したスクリプトが読み込まれ実行されます。次に,実行されたスクリプトは引数で受け取ったRobotオブジェクトにコールバック関数を登録します。最後に,コールバック関数を登録するときに指定した条件を満たすと登録したコールバック関数が呼び出される,という流れになります。

ここからは,Hubotの基本的な動作をどのように定義するかについて説明します。

チャットでの発言に反応するhearとrespond

Hubotは,チャット用のbotですから,チャットで誰かが特定のキーワードを発言したときに何らかの処理を実行すると言うのが基本的な使い方だと思います。 このような場合には,Robotクラスのメソッドhearrespondを使用します。hearrespondを使ったコードのサンプルを次に示します。

module.exports = (robot) ->
  robot.hear /foo/i, (msg) ->
    msg.send "bar"

  robot.respond /hoge/i, (msg) ->
    msg.send "fuga"

respondhearの使い方について説明します。

hear

書式を以下に記します。

hear: (マッチさせたい正規表現, 正規表現にマッチしたときに呼び出されるコールバック関数)

hearメソッドは,第一引数で渡した正規表現にマッチする発言がチャット上にポストされた場合に第二引数で渡したコールバック関数を実行します。 上記のサンプルコードの場合は,/foo/iにマッチする発言があった場合にコールバック関数が呼び出され,Hubotが"bar"と発言します。

図2 hearのイメージ

図2 hearのイメージ

正規表現にマッチするならどのような発言にも反応してしまうので,たとえば,

module.exports = (robot) ->
  robot.hear /deploy/i, (msg) ->
    # デプロイ処理

と言ったコードを書くと,会話の流れでdeployという単語が出ただけでデプロイプロセスが走ってしまうといった問題が発生する可能性があります。 そのため,hearメソッドは,たとえば「誰かがURLがポストするとページを取得してタイトルをポストする」といった無差別にマッチしても問題無い用途で使用すると良いと思います。

respond

書式を以下に記します。

respond: (マッチさせたい正規表現, 正規表現にマッチしたときに呼び出されるコールバック関数)

respondも,正規表現にマッチする発言がチャット上にポストされるとコールバック関数が実行されるという点ではhearと同じです。 しかし,respondの場合はHUBOT_NAMEかそのエイリアスが「発言文字列の先頭」に存在するかどうかのチェックが入るという点が異なります。 たとえば,正規表現が/hoge/iだった場合,respondではhogeという発言にはマッチせず,hubot hogeなどHUBOT_NAMEもしくはHUBOT_ALIASが文頭に存在していた場合にのみマッチするということです。

図3 respondのイメージ

図3 respondのイメージ

respondは,hearとは逆に,おもにHubotに対して特定の命令を実行させたい場合に使います。ちなみに,文頭に付けるHUBOT_NAMEは通常のhubot hogeと言った形式の他にも@hubot hogehubot: hogeといった異なった形式も使用できます。

注意点として,respondメソッドは,渡された正規表現を分解して文頭にHUBOT_NAMEが存在するかという条件を元々の正規表現の先頭に追加した形で正規表現を再構築するため,^を使用しても動作しないといった問題に遭遇することがあります。

チャットに発言を投稿するsendとreply

hearrespondと同様にHubotにチャットで発言させる方法にもsendreplyの2種類があります。

sendreplyを使ったコードのサンプルを次に示します。

module.exports = (robot) ->
  robot.hear /foo/i, (msg) ->
    msg.send "bar"

  robot.respond /hoge/i, (msg) ->
    msg.reply "fuga"

これまで書いてきたコールバック関数は,すべてmsgという仮引数を持ちますが,この引数にはResponseクラスのインスタンスが渡されます。Hubotのスクリプトでは,このResponseオブジェクトを通じてイベントが発生したときの状況を取得したり,チャットツールに対して様々なアクションを取ることができます。

チャットに発言するには,Responseクラスのメソッドsendもしくはreplyを使用します。

send

書式を以下に記します。

send: (発言内容...)

sendメソッドにHubotに発言させたい文字列を渡すことでHubotに発言させることができます。 引数は可変長となっており,複数の文字列を渡すことで複数の発言を同時に送ることもできます。

図4 sendのイメージ

図4 sendのイメージ

reply

書式を以下に記します。

reply: (発言内容...)

replyメソッドを呼び出すと,sendメソッドと同様にHubotが発言しますが,sendメソッドとは「イベントが発生した元となった発言者に対しての返答」という形で発言する点が異なります。 この「返答」の定義はチャットツールやAdapterによって異なります。たとえばhubot-ircでは元の発言者名をHubotの発言の文頭に付与します。

図5 replyのイメージ

図5 replyのイメージ

著者プロフィール

大谷和史(おおたにかずふみ)

色々な物を動かして試してみることが好きなWeb開発者。

現在は,メドピア(株)で日本の医療を良くするために働いています。

URL:http://blog.fumiz.me/

コメント

  • ご指摘いただき誠にありがとうございます

    tesujiro様

    ご指摘いただき誠にありがとうございます。

    ご連絡が遅れまして誠に申し訳ございませんが、修正いたしましたのでご確認いただければ幸いです。

    それでは、これに懲りず弊社をよろしくお願い申し上げます。

    Commented : #2  担当編集者 (2014/08/20, 13:52)

  • typo

    良記事ありがとうございまs。
    以下のページにtypoがありました。

    ページ:
    http://gihyo.jp/dev/serial/01/hubot/0004?page=2

    s/Listner/Listener/

    Commented : #1  tesujiro (2014/08/11, 08:26)

コメントの記入