id:anatooのブログ RSSフィード Twitter

 

2012年2月4日

github社製ボットフレームワーク、hubotをIRCボットとして導入した話

f:id:anatoo:20120204235436p:image

github社製のボットフレームワーク、hubotをIRCボットとして導入する話。

インストール

hubotはnode.jscoffescriptで書かれたgithub社製のボットフレームワーク。IRCに限らず、campfireやtwitterなどいくつかのアダプタに対応しているが、ここではIRCで利用するための導入手順を記す。

あらかじめnode.jsnpmをインストールしておいて、hubotをインストールする。

$ git clone git://github.com/github/hubot.git
$ cd hubot
$ npm install

hubotは、ボットが扱うデータを永続化するためにredisというKVSを利用する。redisが入っていない時はエラーになるので動かす前にインストールする。

ちょっと試してみたいけどredisまで入れるのは面倒という時は、hubot/hubot-scripts.jsonから "redis-brain.coffee" を取り除く。

$ cat hubot-script.json
["redis-brain.coffee", "tweet.coffee", "shipit.coffee"]
$ vim hubot-script.json
$ cat hubot-script.json
["tweet.coffee", "shipit.coffee"]

ここできちんとインストールできたかどうか試すために以下みたいにhubotを起動してみる。

$ cd hubot
$ ./bin/hubot

Hubot>

このコマンドラインインターフェイスは、主にデバッグ用や動作確認用に利用する。特にエラーが出なければ成功。最初から利用できるコマンドの一覧はhubot helpで見る。

Hubot> hubot help
Hubot> <keyword> tweet - Returns a link to a tweet about <keyword>
<user> is a badass guitarist - assign a role to a user
<user> is not a badass guitarist - remove a role from a user
(中略)
who is <user> - see what roles a user has
youtube me <query> - Searches YouTube for the query and returns the video

試しにhubot img hogeとかやってみると、hogeで検索して出てくる画像のurlが帰ってくる。

Hubot> hubot img hoge
http://www.wrapupp.com/wp-content/uploads/2011/08/DM_090513_NFL_HOGEonTAYLOR.jpg#.png

hubot用のスクリプトはhubot-scriptsという別のリポジトリにたくさん収められているので適当に物色する。

IRCアダプタの導入

次はhubotをIRCチャンネル上で動かすためのアダプタを導入する。

IRC用のアダプタはgithubの別のリポジトリに公開されているので、それのインストール手順に従って、hubot/package.jsonの依存関係にhubot-ircを追加してnpm installすると導入できる。

$ cd hubot
$ cat package.json | tail -n 8
  "dependencies": {
    "hubot": "2.1.0",
    "hubot-scripts": "2.0.2",
    "optparse": "1.0.3",
    "hubot-irc": "0.0.6" // <= 追加
  }
}
$ npm install

どのIRCサーバとチャンネルで利用するかなどの設定を含んだ以下のような起動スクリプトを書く。

#!/bin/bash
# runhubot

export HUBOT_IRC_NICK="hubot"
export HUBOT_IRC_ROOMS="#all,#hoge,#fuga"
export HUBOT_IRC_SERVER="irc.example.com"
export HUBOT_IRC_PASSWORD="hoge"

/path/to/hubot/bin/hubot -a irc

起動。

$ ./runhubot &

IRCチャンネルを確認してhubotがいれば成功。

hubotを使ってコマンドを書く

hubotはボットフレームワークであるので、適当に自分でなんかコマンドを作ることが出来る。手っ取り早く何かコマンドを作る場合は、hubot/scirptsディレクトリ以下に.coffeeファイルか.jsファイルを置くと起動時に読み込んでくれる。

以下は、hello worldとチャットに書くと「こんにちは世界!」と返してくれるコマンドの例。インストールした先のhubot/scripts以下にはすでにデフォルトで利用できるコマンドがたくさんあるのでそれらを見つつコマンドを作っていくと良い。

# helloworld.coffee

module.exports = (robot) ->

  robot.hear /hello world/, (msg) ->
    msg.send "こんにちは世界!"

hubotを再起動して試してみる。

$ ./bin/hubot

Hubot> hello world
Hubot> こんにちは世界!

coffeescript使いたくないおとかいう人は普通にJavaScriptでも書ける。

// hoge.js

module.exports = function(robot) {
    robot.hear(/hoge/, function(msg) {
        msg.send('hogehoge-');
    });
};

ボットのデータを永続化する

hubotを起動しなおしても、以前のデータを保持し続けたい場合は、以下みたいにrobot.brain.data以下にデータを登録して、robot.brain.save()するとデータが永続化される。

module.exports = (robot) ->

  robot.hear /hoge/, (msg) ->
    unless robot.brain.data.hoge
      robot.brain.data.hoge = ''
    robot.brain.data.hoge += "hogehoge"
    robot.brain.save

    msg.send robot.brain.data.hoge

試してみたのが以下。

$ ./bin/hubot

Hubot> hoge
hogehoge
Hubot> hoge
hogehogehogehoge
Hubot> exit

$ ./bin/hubot

Hubot> hoge
hogehogehogehogehogehoge

再起動してもデータが消えていないことがわかる。

定期的になにか喋ってもらうためにnode-cronを導入する

hubotだと、今のところ何らかのユーザ側の発言が無ければ何もアクションを起こしてくれない。ボットに朝の挨拶をしたり定期的に何かのURLを流したりして欲しかったのでnode-cronを導入した。

$ cd hubot
$ npm install cron

んで、定期的に何かを喋らせる例が以下。これもまたhubot/scripts以下に置く。

cron = require('cron').CronJob

module.exports = (robot) ->
  # utility function
  send = (msg) -> (new robot.Response(robot, {}, "")).send msg

  cron '* * 11 * * *', () ->
    send "11時ですよー"

これで、ボットが定期的に挨拶してくれるようになった。

日本語環境でhubotをIRCボットとして利用する際の注意

node.js自体が、UTF-8以外の文字コードを扱うのがあまり得意じゃないせいか、ISO-2022-JPでIRCチャンネルを利用しているところでhubotを動かすと文字列の扱いがおかしくなる。っていうか普通に文字化けする。これはhubot-ircがUTF-8以外の文字列を全く考慮していないせいなので、とりあえずの解決策としてISO-2022-JPで喋らせたい場合はtiarraなどのIRCプロクシを立ててそれを経由させるのが賢明かもしれない。

終わり

この記事では、github社製のボットフレームワークであるhubotをIRCボットとして導入するための手順などを書いた。IRCは開発者間のコミュニケーションのインフラとして利用できる。そのIRCをより便利に扱うためにIRCボットやikachanIRCBot Consoleなどの周辺ツールを導入することは、開発者間のコミュニケーションをより促進してくれるものと思われる、まる。

2012年1月26日

ikachanでgitのpushをIRCチャンネルに通知する (追記アリ)

gitのpushがあるたびに、コミットをIRCで通知するようにする。

まずikachanサーバを立てる

適当なサーバにikachanをインストールする。ikachanはIRCにメッセージを通知するためのサーバで、これを設置することで、シェルスクリプトなどから簡単にIRCにメッセージをポストできる。

例えば、以下の様にcurlコマンドを叩くだけでIRCに通知できるようになる。

$ curl -F channel=\#catalyst-ja http://localhost:4979/join
$ curl -F channel=\#catalyst-ja -F message=hoge! http://localhost:4979/notice

gitのコミットにかかわらずちょっとした通知をガンガンIRCチャンネルに通知できるので便利です。

pushされるリポジトリにhookスクリプトを置く

以下のようなhookスクリプトを置く。共有gitリポジトリ以下のhooks/updateに置いて実行権限を与えると動作する。

#!/bin/sh

# repository name
REPO_NAME="hoge.git"

# IRC channnel
CHANNEL="#hoge"

# ikachan server
IKACHAN="http://172.16.1.22:4979"

curl -s -F channel=$CHANNEL -F message="$REPO_NAME#$1 updated:" $IKACHAN/notice 1> /dev/null &

git rev-list $3 ^$2 --reverse | while read COMMITID
do
    MSG=`git --no-pager log -1 --format='%an: %s' $COMMITID`
    curl \ -s -F channel=$CHANNEL -F message="$MSG" $IKACHAN/notice 1> /dev/null & 
done

チャンネル名やメッセージなど適当に書き換えて使う。実際に自分が使っている例だと、メッセージと一緒に、gitwebのdiff画面へのリンクも通知したりしている。

ちなみに、gitリポジトリをWebDAVでホストしてるとサーバサイドフックが起動されないので注意。sshかSmartHTTP経由でリポジトリはホストしましょう。

終わり

こういう通知スクリプトを置くと、IRCチャンネルを見てるとなんとなくgitリポジトリの動きがわかるようになる。いちいちgitリポジトリのウェブインターフェイスにアクセスするような手間が省けるし、人から自分のコミットのコードを見てもらえる頻度も高くなるのでおすすめ。 あとikachan++です。


追記: 1/31 非同期でフックを実行する

上のフックスクリプトの場合、IRCへの通知が同期的に行われるのでpushが終了するのに処理が終了するのを待たなければならない。gitのhookでブロックされるのとかありえん、などと各所から言われた気がしたのでもうすこしマシな方法を調べてみた。

単に上のスクリプトを外出しして nohup /path/to/command &で非同期に実行してやればいいだけでしょとか思って試したら、シェルスクリプト自体は非同期で実行されているっぽいものの、なぜか通知処理が終了するまでpushが終わらない。シェルスクリプトに関する知識が足りないせいか何故かよく分からなかった。だれか教えて下さい。

もう少し調べた結果、以下みたいに単にatコマンドに実行したいコマンドを流してやればいいことがわかった。

#!/bin/sh

# /path/to/notificationは上に書いたスクリプトを外出ししたもの
echo "/path/to/notification $1 $2 $3" | at now

atコマンドは指定した時間に一度だけコマンドを発行してくれるコマンドだが、at nowにコマンドを指定することで、別プロセスで即座にコマンドが実行される。これで、通知処理が非同期で実行されるようになってブロックされなくなりgitのpushが早くなった。よかったですね、まる。

2012年1月19日

JavaScriptでJSONからオブジェクトをデシリアライズする

PHPのunserializeみたいに、JavaScriptでJSONからオブジェクトのデシリアライズする方法。とりあえずこうやってるけど、誰かなんかいいソリューション教えて下さい。これだと、同じオブジェクトを参照しているプロパティがデシリアライズした後、違うオブジェクトを指すようになる + 自分でクラス名を指定してあげないといけない。

// Hogeクラスの作成
var Hoge = function(msg) { this.msg = msg; };
Hoge.prototype = {
  doSomething : function() { 
    console.log(this.msg);
  }
};

Hoge.deserialize = function(json) {
  var result = JSON.parse(json);
  result.__proto__ = Hoge.prototype;
  return result;
};

// JSONからHogeクラスのオブジェクトを復元する
Hoge.deserialize(JSON.stringify(new Hoge('hoge'))).doSomething(); // => hogeと表示される