これは Hubot Advent Calendar 2014 の 10 日目の記事です。
また、今回は @bouzuya の Hubot 連載の第 8 回です。目次は、第 1 回の記事にあるので、そちらをどうぞ。
前回まで、そして今回は
前回は スクレイピングする Hubot スクリプトをつくろう ということで、npm パッケージ request
と cheerio
とを使って、Adventar をスクレイピングして Advent Calendar 一覧を返す Hubot スクリプトをつくりました。
前回は HTTP リクエストしたので、今回は HTTP レスポンスしましょう (?)。 Hubot で HTTP アクセスを待ち受けて、HTML を返しましょう。要するに Hubot でホームページ (Web サーバー) をつくりましょう。
スクリプトの完成イメージ
まず、今回つくろうとしている Hubot スクリプトのイメージを共有しましょう。
今回は簡単なリンク集をつくります。チャットでの実行イメージはこんな感じです。
bouzuya> hubot homepage add bouzuya http://bouzuya.net/
hubot> added bouzuya http://bouzuya.net/
bouzuya> hubot homepage list
hubot> [0] bouzuya http://bouzuya.net/
bouzuya> hubot homepage remove 0
hubot> removed bouzuya http://bouzuya.net/
また Web ブラウザで http://<hubot url>/hubot-homepage/
を見るとリンク集が表示されます。
さっそくつくる
まずは Hubot スクリプトのテンプレートを generator-hubot
で生成します。
$ mkdir hubot-homepage
$ cd hubot-homepage
$ yo hubot:script
...
説明不要ですね。
今回は追加パッケージは不要です。 Hubot は元々 express 3.x に依存しており、HTTP の待ち受けも標準でできるようになっているからです。
さっそく書いていきましょう。
# Description
# A Hubot script for hosting your web page.
#
# Configuration:
# None
#
# Commands:
# hubot homepage list - list links in homepage
# hubot homepage add <text> <url> - add link to homepage
# hubot homepage remove <index> - remove link from homepage
#
# Author:
# bouzuya <m@bouzuya.net>
module.exports = (robot) ->
links = []
robot.router.get '/hubot-homepage/', (req, res) ->
li = links.map (i) ->
"""
<li><a href="#{i.url}" target="_blank">#{i.text}</a></li>
"""
.join '\n'
html = """
<html>
<head><title>Links</title></head>
<body>
<h1>Links</h1>
<ul>
#{li}
</ul>
</body>
</html>
"""
res.type 'html'
res.send html
robot.respond /homepage li?st?/, (msg) ->
msg.send links.map((i, index) -> "[#{index}] #{i.text} #{i.url}").join('\n')
robot.respond /homepage add (.+) (https?:\/\/.+)/, (msg) ->
text = msg.match[1]
url = msg.match[2]
item = { text, url }
links.push item
msg.send "added #{item.text} #{item.url}"
robot.respond /homepage re?m(?:ove)? (\d+)/, (msg) ->
index = msg.match[1]
item = links.splice(index, 1)[0]
msg.send "removed #{item.text} #{item.url}" if item?
解説
ええと、解説します。
links = []
リンクをここに保持します。永続化はしていないため、Hubot を再起動すると消えてしまいます。
本来は Hubot の brain で永続化すべきなのですが、それはまた別の機会に紹介します。
robot.router
Node.js express に慣れたユーザーに説明するなら、これは express()
です。express
は Node.js のデファクトスタンダードな Web アプリケーションフレームワークです。Ruby の sinatra のような軽量のフレームワークです。
想像どおり express の解説をするときりがないので、簡単に。
robot.router.get(path, callback)
で GET
メソッドを待ち受けて callback
で処理できます。robot.router.post(path, callback)
などもあります。詳しくは express の api reference を参照してください。
注意事項としては express のバージョンは 3.x なので、それに対応したドキュメントを参照し、コードを記述してください。
/homepage li?st?/
/homepage re?m(?:ove)? (\d+)/
細かいことですが、コマンドを省略できるようにしています。
list
は ls
でも認識されますし、remove
は rm
でも認識されます。
Hubot のコマンドはなるべく入力しやすく工夫すると良いと思います。
その他
あとは特にはないですね。list
/ add
/ remove
をそれぞれ定義し、links
を操作できるようにしています。
では、動かしてみましょう。
$ HUBOT_SHELL_USER_NAME='bouzuya' PATH="./node_modules/hubot/node_modules/.bin:$PATH" $(npm bin)/hubot -a shell -n hubot -r src
hubot> hubot homepage list
hubot> hubot homepage add bouzuya http://bouzuya.net/
added bouzuya http://bouzuya.net/
hubot> hubot homepage add blog http://blog.bouzuya.net/
added blog http://blog.bouzuya.net/
hubot> hubot homepage add google http://www.google.com/
added google http://www.google.com/
hubot> hubot homepage list
[0] bouzuya http://bouzuya.net/
[1] blog http://blog.bouzuya.net/
[2] google http://www.google.com/
hubot> hubot homepage rm 1
removed blog http://blog.bouzuya.net/
hubot> hubot homepage list
[0] bouzuya http://bouzuya.net/
[1] google http://www.google.com/
hubot>
動きそうですね。
Web ブラウザで http://localhost:8080/hubot-homepage/
にアクセスしてみてください。以下のような Web ページが表示されます。
登録した links が表示されます。画像は 3 件登録した直後のものです。もちろん削除も反映されます。
まとめ
今回は robot.router
を使って HTTP リクエストを待ち受けてみました。
Hubot はホームページもつくれるんですね!すごい!(のか?)
ちなみに、この機能を使って、外部サービスの Web hook へ対応したり、adapter の実装に使ったり、生成した画像を一時的にホストして返す、などができます。BOT は必ずしも HTTP の待ち受けの必要はないので、削除すべき、といった意見もありますが、互換性の問題もありますし、おそらく簡単には削除されないと思います。
ちなみに今回のサンプルは bouzuya/hubot-homepage にあります。うまく動かない、などがあれば参考にどうぞ。
最後に
サンプルをつくるのが大変になってきました。
あとは brain
/ adapter
くらいですかね。起動スクリプトの動きとか、スクリプト読み込みとか、Hubot 本体の動きを追っていく感じになりますかね。
次回は brain かな。正直、クソ仕様が山ほどありますし、説明する上では準備が面倒なので、個人的には嫌いなんですが。