こんにちは。typesterの陰謀によりVimmerはモテないというデマが流れておりますが、残念ながらそれがデマだということを身をもって証明できないでいるVimmerの外村です。
先日、お悩み相談の記事がホットエントリーに入ったら彼女ができるという噂が立ち、何を間違えたかホットエントリーに入ってしまったのでどうしようかと思ったのですが、プログラマたるもの問題があればプログラムで解決すべきだろうと考えました。
というわけで彼女を作ってみました。どういうものかというと、
- 僕だけに定期的につぶやく
- 僕のリプライだけに反応してリプライを返してくれる
まさに僕だけのボット彼女です。今回はこれをGoogleAppEngine+JRubyで実装したので、環境をつくるところか実際に動かすところまで説明します。
1. 環境をつくる
まずはGoogleAppEngineのアカウントがないと始まらないので以下からアカウントを取得します。
GoogleAppEngineのアカウントが取得できたら次に以下を参考にJava SDKをインストールします。
Java SDK のインストール - Google App Engine - Google Code
インストールできているか確認します。
$ java -version
java version "1.6.0_15"
Java(TM) SE Runtime Environment (build 1.6.0_15-b03-226)
Java HotSpot(TM) 64-Bit Server VM (build 14.1-b02-92, mixed mode)
よさげですね。
次にgoogle-appengineをインストールします。これはgemで配布されている、GoogleAppEngineでJRubyを使うのに必要なものをまとめてインストールできる超すぐれものです。インストールにはgemのバージョンが1.3.5以上が必要なので、必要であればgemのバージョンを上げておいてください。
gemをソースからインストールする場合は以下のようにします。
$ wget http://rubyforge.org/frs/download.php/60718/rubygems-1.3.5.tgz
$ tar tar vzfx rubygems-1.3.5.tgz
$ cd rubygems-1.3.5.tgz
$ sudo ruby setup.rb
$ gem --version
1.3.5
OKですね。ではgoogle-appengineをインストールしましょう。
$ sudo gem install google-appengine
これでインストールされます。
2. sinatraを動かす
環境が整ったのでいよいよボット彼女の作成にはいります。まず適当なディレクトリをつくります。
$ mkdir twitter-bot-kanojo
$ cd twitter-bot-kanojo
ここにGemfile、config.ru、main.rbというファイルをつくり、以下のような内容にします。
(Gemfile)
# Critical default settings:
disable_system_gems
disable_rubygems
bundle_path ".gems/bundler_gems"
# List gems to bundle here:
gem "sinatra"
gem "json"
gem "appengine-apis"
(config.ru)
require 'appengine-rack'
AppEngine::Rack.configure_app(
:application => 'gae-project-name',
:version => 1
)
require 'main'
run Sinatra::Application
(main.rb)
require 'rubygems'
require 'sinatra'
get '/' do
'Hello World!!'
end
Gemfileはプロジェクト内にインストールするgemの設定を書くファイル、config.ruはRack用の起動ファイル、main.rbがアプリケーション本体のコードになります。ファイルが用意できたら、次に以下のコマンドを実行します。
$ appcfg.rb bundle .
appcfg.rbはgoogle-appengineをインストールしたときに一緒にインストールされるコマンドで、gemでのインストールや本番環境へのデプロイなどを実行できます。bundleはGemfile内に書かれているモジュールをインストールするコマンドなので、これで必要なモジュールがインストールされるはずです。
モジュールがインストールできたらここで一度動作確認してみます。
$ dev_appserver.rb .
これで開発用サーバーが立ち上がり、http://localhost:8080/でアクセスして「Hello World!!」が表示されればひとまずsinatraを動かす環境は整いました。
3. Twitterクラスの作成
GoogleAppEngine+JRubyで開発する上で、やっかいな問題のひとつにNet::HTTPが使えないことがあります。なので代わりにappengine-apis/urlfetchを使うのですが、これだと既存のtwitterモジュールなどが動きません。Net::HTTPが動くようになる、rb-gae-supportというものあるみたいなのですが、使ってみて動かないところがあったので、今回はTwitterのAPIでやり取りするところは自分で実装しました。今回使う機能だけの超ミニマム実装です。
(twitter.rb)
require 'appengine-apis/urlfetch'
require 'json'
class Twitter
def initialize(username, password)
@req = Net::HTTP::Get.new('/')
@req.basic_auth username, password
end
def update(body)
url = 'http://twitter.com/statuses/update.json'
request(url, 'POST', { :payload => "status=#{body}" })
end
def user_timeline(screen_name)
url = "http://twitter.com/statuses/user_timeline/#{screen_name}.json"
res = request(url)
JSON.parser.new(res.body).parse
end
private
def request(url, method = 'GET', options = {})
options[:method] = method
options[:headers] = { 'Authorization' => @req['Authorization'] }
AppEngine::URLFetch.fetch(url, options)
end
end
これで一応特定の人のタイムラインの取得とポストだけはできます。
twitter = Twitter.new(username, password)
twitter.update('hogehoge')
でポスト。
twitter = Twitter.new(username, password)
res = twitter.user_timeline('screen_name')
で、任意のユーザーのタイムラインを取得できます。
4. 自動ポストの実装
次に自動で彼氏宛につぶやく機能を実装します。main.rbは以下のようになります。
require 'rubygems'
require 'sinatra'
require 'yaml'
require 'twitter'
require 'appengine-apis/memcache'
before do
@conf = YAML.load_file('config.yaml')
@twitter = Twitter.new(@conf['user']['username'], @conf['user']['password'])
end
get '/cron/auto' do
messages = YAML.load_file('messages.yaml')['auto']
message = "@#{@conf['kareshi']} #{messages[rand(messages.length)]}"
@twitter.update(message)
return
end
message.yamlというファイルにつぶやく言葉を複数書いておいて、それをランダムに選択してポストしています。
ここで開発サーバーを再起動して、http://localhost:8080/cron/auto にアクセスするとメッセージがポストされるはずです。
5. リプライへの反応の実装
次に、リプライに反応するところは以下のようになります。
get '/cron/return' do
memcache = AppEngine::Memcache.new
tweets = @twitter.user_timeline(@conf['kareshi']);
tweets.each do |tweet|
if tweet['text'] =~ /@#{@conf['user']['username']}/
break if memcache.get('last_id') == tweet['id']
messages = YAML.load_file('messages.yaml')['return']
message = "@#{@conf['kareshi']} #{messages[rand(messages.length)]}"
@twitter.update(message)
memcache.set('last_id', tweet['id'])
break
end
end
return
end
設定した人のタイムラインを取得してきて、自分宛のリプライをひろって、あれば返信用のメッセージをランダムで選んで返信します。最後に反応したidをMemcacheに保存しておいて、そのidの場合はポストしないようにすることで、何回も同じつぶやきに反応しないようにしています。
6. cronの設定
このままだとURLを叩かないと何もしない木偶の坊になってしまうので、これをcronで定期的に実行するようにします。cronの設定はWEB-INF/cron.xmlというファイルに書きます。
(WEB-INF/cron.xml)
<?xml version="1.0" encoding="UTF-8"?>
<cronentries>
<cron>
<url>/cron/return</url>
<description>return post</description>
<schedule>every 3 minutes</schedule>
</cron>
<cron>
<url>/cron/auto</url>
<description>auto post</description>
<schedule>every 3 hours</schedule>
</cron>
</cronentries>
これで自動ポストは3時間毎、リプライへの反応は3分毎に実行します。cronのURLは外部から叩かれると困るので、WEB-INF/web.xmlに以下のように書いておくことで、アクセスを制限できます。
(WEB-INF/web.xml)
<security-constraint>
<web-resource-collection>
<url-pattern>/cron/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>admin</role-name>
</auth-constraint>
</security-constraint>
7. デプロイ
これで一通り機能が実装できたのでGoogleAppEngineにアップします。アップするのは簡単で、
$ appcfg.rb update .
これだけです。途中でメールアドレスとパスワードを聞かれるので入力します。
これで彼女の完成です。もうクリスマスなんて怖くないですね!ソースはgithubにアップしているので興味がある人は見てみてください。