今回は、Hubotのスクリプトが動く仕組みについて説明し、基本的な機能であるチャットでの受け答えを実装する方法を説明した後に、その他の機能について紹介します。
スクリプトの基本
Hubotがスクリプトを読み込み実行する仕組みを説明するために、“hello”と挨拶するとHubotが“hi”と返事する単純なスクリプトのサンプルを示します。
このサンプルコードの一番外側を見ると、module.exports
に関数を代入しています。このmodule.exports
は、Node.jsでモジュールを作るための仕組みです。つまり、Hubotのスクリプトとは、引数を1つとる1つの関数を提供するNode.jsのモジュールということになります。
サンプルコードでは、関数の中で引数robot
のhear
メソッドを呼び出してコールバック関数を登録しています。このrobot
は、Hubotの本体とも言えるRobotクラスのインスタンスです。
このように、基本的なHubotのスクリプトでは、Robot
オブジェクトのメソッドを通じてコールバック関数を登録することで、「チャット上で誰かが特定のキーワードを発言したとき」や「チャットルームに誰かが新たに参加したとき」など様々な状況ごとにHubotの振る舞いを定義していきます。
Node.jsのモジュールの詳しい仕組みは、Node.jsのドキュメントをご確認ください。
スクリプトの読み込み
では、作成したモジュール、つまりHubotのスクリプトはいつどのように読み込まれ実行されるのでしょうか。
Hubotが起動してAdapterがチャットツールと接続したとき、Hubotは./scripts
、./src/scripts
、hubot-scripts.json
, external-scripts.json
、HUBOT_SCRIPTSで指定したpath
からNode.jsのモジュールを探し、require
すると同時にmodule.exports
に代入された関数をRobot
オブジェクトを引数にして呼び出します。
Hubot(Robotオブジェクト)が特定のスクリプトファイルを読み込むコードの例を次に抜粋します。
216行目で、読み込み対象のモジュールをrequire
しつつexportされた関数を自分自身を引数として呼び出していることがわかります。
まとめ
スクリプトの登録から呼び出しまでの流れを図1に示します。
まとめると、まずHubotが起動してAdapterがチャットツールに接続したときに作成したスクリプトが読み込まれ実行されます。次に、実行されたスクリプトは引数で受け取ったRobot
オブジェクトにコールバック関数を登録します。最後に、コールバック関数を登録するときに指定した条件を満たすと登録したコールバック関数が呼び出される、という流れになります。
ここからは、Hubotの基本的な動作をどのように定義するかについて説明します。
チャットでの発言に反応するhearとrespond
Hubotは、チャット用のbotですから、チャットで誰かが特定のキーワードを発言したときに何らかの処理を実行すると言うのが基本的な使い方だと思います。 このような場合には、Robot
クラスのメソッドhear
とrespond
を使用します。hear
とrespond
を使ったコードのサンプルを次に示します。
respond
とhear
の使い方について説明します。
hear
書式を以下に記します。
hear
メソッドは、第一引数で渡した正規表現にマッチする発言がチャット上にポストされた場合に第二引数で渡したコールバック関数を実行します。 上記のサンプルコードの場合は、/foo/i
にマッチする発言があった場合にコールバック関数が呼び出され、Hubotが"bar"と発言します。
正規表現にマッチするならどのような発言にも反応してしまうので、たとえば、
と言ったコードを書くと、会話の流れで“deploy”という単語が出ただけでデプロイプロセスが走ってしまうといった問題が発生する可能性があります。 そのため、hear
メソッドは、たとえば「誰かがURLがポストするとページを取得してタイトルをポストする」といった無差別にマッチしても問題無い用途で使用すると良いと思います。
respond
書式を以下に記します。
respond
も、正規表現にマッチする発言がチャット上にポストされるとコールバック関数が実行されるという点ではhear
と同じです。 しかし、respond
の場合はHUBOT_NAMEかそのエイリアスが「発言文字列の先頭」に存在するかどうかのチェックが入るという点が異なります。 たとえば、正規表現が/hoge/i
だった場合、respond
では“hoge”という発言にはマッチせず、“hubot hoge”などHUBOT_NAMEもしくはHUBOT_ALIASが文頭に存在していた場合にのみマッチするということです。
respond
は、hear
とは逆に、おもにHubotに対して特定の命令を実行させたい場合に使います。ちなみに、文頭に付けるHUBOT_NAMEは通常の“hubot hoge”と言った形式の他にも“@hubot hoge”や“hubot: hoge”といった異なった形式も使用できます。
注意点として、respond
メソッドは、渡された正規表現を分解して“文頭にHUBOT_NAMEが存在するか”という条件を元々の正規表現の先頭に追加した形で正規表現を再構築するため、^
を使用しても動作しないといった問題に遭遇することがあります。
チャットに発言を投稿するsendとreply
hear
とrespond
と同様にHubotにチャットで発言させる方法にもsend
とreply
の2種類があります。
send
とreply
を使ったコードのサンプルを次に示します。
これまで書いてきたコールバック関数は、すべてmsg
という仮引数を持ちますが、この引数にはResponseクラスのインスタンスが渡されます。Hubotのスクリプトでは、このResponse
オブジェクトを通じてイベントが発生したときの状況を取得したり、チャットツールに対して様々なアクションを取ることができます。
チャットに発言するには、Response
クラスのメソッドsend
もしくはreply
を使用します。
send
書式を以下に記します。
send
メソッドにHubotに発言させたい文字列を渡すことでHubotに発言させることができます。 引数は可変長となっており、複数の文字列を渡すことで複数の発言を同時に送ることもできます。
reply
書式を以下に記します。
reply
メソッドを呼び出すと、send
メソッドと同様にHubotが発言しますが、send
メソッドとは「イベントが発生した元となった発言者に対しての返答」という形で発言する点が異なります。 この「返答」の定義はチャットツールやAdapterによって異なります。たとえばhubot-ircでは元の発言者名をHubotの発言の文頭に付与します。
発言内容のキャプチャ
ここまでの内容で、チャット上の特定のキーワードに反応して処理を実行したり、それに合わせてHubotに発言させることができるようになりましたが、キーワードごとに固定の処理しかできないのでは不便です。Hubotは、hear
、respond
のどちらを使用した場合でも、正規表現にマッチした発言の内容を取得して使用することができます。
発言内容をキャプチャするコードのサンプルを次に示します。
respond
、hear
の第一引数に渡す正規表現がチャット上の発言にマッチした場合、コールバック関数の引数に渡されるResponse
オブジェクトのmatch
プロパティにString.matchメソッドの戻り値が格納されます。
たとえば、上記のサンプルコードに対して“hubot deploy web01”と発言した時のmsg.match
の中身を次に示します。
普通にString.match
メソッドを使用した場合と同じく、0番目の要素にマッチした文字列全体が、1番目以降にキャプチャした文字列が格納されます。キャプチャした文字列を使用することで、単純にキーワードに反応するだけでなく、発言内容に応じた様々な処理を実装できます。
その他
Hubotのスクリプトでは、ここまでで紹介した内容の他にも様々な機能を実装できます。すべてを紹介することはできないので、ここではいくつかの項目に絞って説明します。
チャットルームの入退室に反応する
Adapterが対応している場合、チャットルームへ誰かが入退室した時にHubotに何らかのアクションを起こさせることができます。
トピックの変更に反応する
Adapterが対応している場合、チャットルームのトピックが変更された場合に何らかのアクションを起こさせることができます。
なお、hubot-ircは本稿執筆時点では対応していません。
環境変数からの設定の読み込み
前回、Hubotのおもな設定は環境変数を通じて行うと説明しました。 自作のスクリプトでも、通常のNode.jsで行うのと同様にprocess.env
を使用することで環境変数に設定された値を読み取ることができます。
環境変数を読み取る例を次に示します。
エラーハンドリング
Hubotの実行中にcatch
されなかった例外が発生した場合、HubotはNode.jsのuncaughtExceptionイベントを受け取って独自のerror
イベントを発生させます。Robot
クラスのerror
メソッドを使用することで、error
イベントが発生した時に独自の処理を実行することができます。
error
イベントを処理する例を次に示します。
また、Robot
クラスのemmit
メソッドを使用することで自分のスクリプト内で意図的にerror
イベントを発生させることもできます。
Responseオブジェクトについて
ここまでの説明でReponse
オブジェクトの使い方をいくらか説明してきましたが、Hubotを使ううえでRobotオブジェクトと並んで使用頻度の高いオブジェクトですので、もう少し詳しい内容を説明します。
メソッド
Response
クラスのメソッド一覧を次に示します。
メソッド名 | 用途 |
send | チャットルームに通常の方法でメッセージを送る |
emote | 感情表現をチャットルームに送る |
reply | 発言者に対する返信としてメッセージを送る |
topic | チャットルームのトピックを変更 |
play | 音を鳴らす(ほぼCampifire専用) |
locked | ログに残らないように発言する(ほぼCampfire専用) |
random | リストを受け取るとそのリストの中からランダムに1つ選択 |
finish | 今回のメッセージに対してリスナの実行を終了 |
http | scoped-http-clientを使用してHTTPリクエストを作成 |
これらの多くはAdapterのメソッドを呼び出しているため、具体的な動作はAdapterの実装に左右される点には注意が必要です。
playなどのあまり見かけないような機能を持っているのは、おそらくHubotが元々Campfire用のツールとして作られていたことによるようです(Campfireには音を鳴らす機能があります)。 他にも、lockedメソッドもCampfire用のAdapterには実装がありますが、本稿執筆時点では元となるAdapterクラスにはメソッド定義が無かったりします。
お詫び
前回、トピックの変更方法についてAdapterを直接操作する方法で紹介していました。
しかし、実際にはResponseオブジェクトのtopicメソッドを使用することでAdapterが対応しているならばAdapter間の互換性を維持した状態でトピックの変更操作を実装できます。
誤った情報を記載してしまい申し訳ありませんでした。
尚、現在は修正済みです。
プロパティ
Response
オブジェクトのプロパティのうち普段使うのは大きく分けてmessage
、match
の2種類です。
このうち、match
はrespond
やhear
など正規表現でマッチさせる種類のイベントの場合はString.matchの戻り値が入っており、それ以外の場合には基本的にtrue
が入っています。 この仕組みは、ListenerとRobotのコードを見るとわかりやすいと思います。
message
プロパティには、イベントの発生源から送られてくるメッセージが入っています。つまり、イベントの種類やAdapterの実装によって中身が変わる場合があります。Response
オブジェクトからmessage
プロパティの内容を抜粋した例を次に示します。
上記の例は、hubot-ircで“user”という名前の参加者が“#chatroom”という名前のチャンネルで“hubot ping”と発言した場合のメッセージです。 とくに、発言者の情報はスクリプトから使用する機会が多いのではないでしょうか。
自分が使用しているAdapterでどのようなメッセージが送られてきているかを確認するには、Adapterのドキュメントやソースコードを読むか、hear
やrespond
などに渡すコールバック関数で引数のmsg
をconsole.log
で出力してみるといった方法が簡単です。
まとめ
今回は、Hubotのスクリプトの基本的な書き方を説明しました。
普段はhear
、respond
とsend
、reply
を使うことが多いと思いますが、Hubotにはあまり使われていない機能を含め色々な機能があるのでぜひ使いこなしてみてください。
次回は、サンプルコードやhubot-scriptsのコードなどを例に様々な応用方法を紹介します。