Ruby+User Stream APIで無言リプライに高速返信するbotを作りました
Twitter User Stream API でタイムラインを表示するサンプル - でぶぬる日記
というエントリを先日書いたのですが、もう少し実用的なプログラムを作ってみました。
このbotを動かしているTwitterアカウントに対して無言@を送信すると、「random.txt」ファイルに書かれたダジャレのうちランダムで一つを選択して自動で返信してくれます。
通常のREST APIによるbotプログラムの場合、一定間隔でAPIをコールしてタイムラインを取得するような作りになる為、@を送ってからリプライが返ってくるまでに多少のタイムラグが出てしまいます。一方、User Stream APIを使用する場合は、タイムラインをほぼリアルタイムで読み取ることが出来る為、高速で応答することが出来るみたいです。
ちなみにダジャレのテキストファイルはこれを使っています。
今回の実装における注意点を列挙しておきます。
User-Agentヘッダをちゃんと設定しておいたほうが良さげ
実装における必須事項ではなさげですが、以下のように書いてあるので設定しておいたほうが無難でしょう。
http://dev.twitter.com/pages/user_streams
The User-Agent HTTP header must be set to include the application’s version. This will be critical in diagnosing issues with your client. If your environment precludes setting this the User-Agent field, you must then set an X-User-Agent field.
trackキーワードを付与しておく
User Stream APIにもtrackキーワードが実は指定出来るようです。
"track=[Twitterアカウントのscreen_name]" というパラメータを付与しておくことで、screen_nameを含むツイートがStream内に混ざってくるようになり、「自分がフォローしていないユーザからの@」も(高い確率で)検出出来るようになるでしょう。
なお、trackパラメータは厳密にはpostで送る必要があるそうですが、現状URLに付与して送信しても動作しているので、簡易的にそのような実装にしています。
再接続処理を実装しておく
前回のサンプルでは省略してしまっていたのですが、まともにUser Stream APIを使ったbotを運用しようと思った場合、Twitterとの接続が切れた際の再接続処理を実装しておく必要があります。
今回のプログラムでは単純に、あらゆる例外発生時に強制的に再接続するという手抜きな実装にしてしまいました。
これだと永久にリトライしてしまうので、本来であればより真面目に実装すべきところではあるかもしれません。
Rubyのnet/httpで発生するTimeout::Errorは、rescueで明示的に指定しないと拾えないようなので、注意が必要です。
参考: Timeout::Errorに注意 - dreammindの日記
ソースコード
# -*- coding: utf-8 -*- require 'rubygems' require 'net/https' require 'oauth' require 'json' if RUBY_VERSION < '1.9.0' class DajareBot # 適当なものを定義すること CONSUMER_KEY = "xxxxxxxx" CONSUMER_SECRET = "xxxxxxxx" ACCESS_TOKEN = "xxxxxxxx" ACCESS_TOKEN_SECRET = "xxxxxxxx" # botを動かすTwitterアカウントのscreen_nameを定義すること MY_SCREEN_NAME = "xxxxxxxx" # botのUser-Agentを指定 BOT_USER_AGENT = "Auto Dajare Reply Program 1.0 @aquarla" # テキストファイルの置き場所を指定 RANDOM_FILE_PATH = "./random.txt" # 証明書のパスを指定 HTTPS_CA_FILE_PATH = './verisign.cer' def initialize @consumer = OAuth::Consumer.new( CONSUMER_KEY, CONSUMER_SECRET, :site => 'http://twitter.com' ) @access_token = OAuth::AccessToken.new( @consumer, ACCESS_TOKEN, ACCESS_TOKEN_SECRET ) # ファイルからダジャレ一覧を読み込み open(RANDOM_FILE_PATH) do |file| @dajares = file.readlines.collect{|line| line.strip} end end # Stream APIの呼出処理 def connect uri = URI.parse("https://userstream.twitter.com/2/user.json?track=#{MY_SCREEN_NAME}") https = Net::HTTP.new(uri.host, uri.port) https.use_ssl = true https.ca_file = HTTPS_CA_FILE_PATH https.verify_mode = OpenSSL::SSL::VERIFY_PEER https.verify_depth = 5 https.start do |https| request = Net::HTTP::Get.new(uri.request_uri) request["User-Agent"] = BOT_USER_AGENT request.oauth!(https, @consumer, @access_token) buf = "" https.request(request) do |response| response.read_body do |chunk| buf << chunk while (line = buf[/.+?(\r\n)+/m]) != nil begin buf.sub!(line,"") line.strip! status = JSON.parse(line) rescue break end yield status end end end end end def run loop do begin connect do |json| if json['text'] user = json['user'] # 無言リプライを検出したら、発言元に対してダジャレをツイートする if (json['text'].match(/^@#{MY_SCREEN_NAME}\s*$/)) @access_token.post('/statuses/update.json', 'status' => "@#{user['screen_name']} #{random_dajare}", 'in_reply_to_status_id' => json['id']) end end end rescue Timeout::Error, StandardError # Timeout::Errorも明示的に捕捉する必要あり puts "Twitterとの接続が切れた為、再接続します" end end end def random_dajare @dajares[rand(@dajares.size)] end end if $0 == __FILE__ DajareBot.new.run end
2012年4月19日追記
最新のソースコードをこちらに公開しました。
Twitter UserStreamから高速ダジャレ返信するbotのソースコードを公開しました - でぶぬる日記