Twitter ボットのエンハンス

複数の Twitter ボット (@housai, @domoraen, @aa_canvas) を作って運用しているんだけど、ソースが汚くて修正しづらい上に、OAuth 対応してなかったり、domoraen に関しては同じリプライに何度も反応してしまうバグがあったり、色々ダメな感じだったので色々エンハンスしました。

エンハンス項目

  • ソースをきれいにする
    • Twitter へ認証情報を送ってポストしたりリプライしたりする部分と、ボットが考えて発言や返事を作ったりする部分が密に結合していて気持ち悪いので、疎結合にして修正しやすくする
  • OAuth 対応
    • 6 月で Twitter API への Basic 認証経由でのアクセスができなくなる (?) らしいので、Basic 認証にしか対応していない Twitter4R に依存するのをやめて OAuth に対応させる
  • domoraen のバグ修正
    • リプライするボットは最後にリプライした id をどこかに保存しておいてリプライ済みのステータスには返事しないようにする必要があって、その id の保存先を YAML ファイルしてたら domoraen がリプライ考えている間ファイルがロックされちゃって id が読めなくなって何度も同じステータスにリプライ返しちゃうみたいなバグがあったので、フバレコ様も使っている*1 TokyoTyrant で読み書きしてロックとか起こらないようにする

作ったライブラリ

上記の項目を総合して、かなり強引な感じのオレオレ Twitter Bot 用ライブラリを作った。

たとえば「単純に 'hello' とポストしたり、自分へのリプライを取得して 'hello' と言われたら 'hello' と返すボット」だったら以下のように書けます:

#!/usr/bin/env ruby
$:.unshift(File.dirname(__FILE__))

require 'bot'

bot = Bot.new('hello_bot') # ボットの Twitter ユーザ名をコンストラクタに与える

bot.add_behavior(:tweet, Proc.new{ 'hello' })
bot.add_behavior(:reply, Proc.new{|r|
  if r['text'].match(/^@hello_bot\s+hello/)
    "@#{r['user']['screen_name']} hello"
  else
    next nil
  end
})

bot.act!

個別ボットのスクリプトでは振る舞いだけを定義できるようにして、内部的な処理 (設定ファイルの読み込み、TokyoTyrant への接続、OAuth 経由での Twitter へのポスト) は全部 bot.rb に任せている感じです。
(ボット用ライブラリではないけど lib-text-converter とか sinatra-openid の「面倒くさい処理を全部ライブラリに任せて呼び出し側はやりたいことに専念する」みたいなコンセプトを参考にしています。)

運用方法

上記のファイルを bot.rb と同じディレクトリに置いて、こんな感じで起動します。

/path/to/hello_bot.rb tweet # Twitter に 'hello' と投稿
/path/to/hello_bot.rb reply # リプライを取得して 'hello' と言われたら 'hello' と返す

初めて起動すると、エディタを開いて OAuth の認証情報と TokyoTyrant の接続情報を聞いてきます (Pit を使ってます)。

---
tokyotyrant_port: tokyotyrant_port here
consumer_key: consumer_key here
consumer_secret: consumer_secret here
access_token_secret: access_token_secret here
tokyotyrant_hostname: tokyotyrant_hostname here
access_token: access_token here

これさえ入力すればあとは TokyoTyrant で最後にリプライした id を読み書きしつつ、OAuth 経由で適当に Twitter へ投稿してくれるようになります。

crontab ビフォーアフター

もとがグダグダすぎたんだけど、実際に運用している crontab がかなり読みやすくなった。

エンハンス前:

0 * * * * /home/tily/bin/housai_twitter_bot/post_housai_haiku.sh
0,30 * * * * /home/tily/svn/MarkovChain/bin/markov speak -c /home/tily/bin/domoraen /config_domoraen.yaml -w 2 | /home/tily/bin/domoraeon/post.rb 2>&1 >/dev/null
0,30 * * * * /home/tily/bin/aa_canvas/post.rb /home/tily/bin/aa_canvas/config.yaml 2>&1 >/dev/null
*/5 * * * * /home/tily/svn/MarkovChain/bin/domoraen_reply /home/tily/svn/MarkovChain/config_doraemon.yaml 2>&1 /dev/null
*/5 * * * * /home/tily/bin/aa_canvas/bot.rb /home/tily/bin/aa_canvas/config.yaml 2>&1 >/dev/null

エンハンス後:

0    * * * * /home/tily/bots/housai.rb    tweet 2>&1 >/dev/null
0,30 * * * * /home/tily/bots/domoraen.rb  tweet 2>&1 >/dev/null
0,30 * * * * /home/tily/bots/aa_canvas.rb tweet 2>&1 >/dev/null
*/5  * * * * /home/tily/bots/domoraen.rb  reply 2>&1 >/dev/null
*/5  * * * * /home/tily/bots/aa_canvas.rb reply 2>&1 >/dev/null

まとめ

ほんとは Gearman も導入してみたかったんだけど、フバレコみたいに極端に重い処理はしていないのでとりあえず保留でいいや。

今回の修正でだいぶ個別のボットに機能追加しやすくなったので、なんか面白い機能を付け加えたり、新しいボットを作ったりしたい。

*1:フバレコは Cabinet のほうでした via http://twitter.com/fuba/status/15546742151