Google WaveのRobotをRubyで作る

昨日の夜にGoogleオフィスでWave Hackathon(?)があった。そのときにWave RobotをGAE/J上でJRuby使ってSinatra上で動かしてみたので、以下にやったことのメモなど。

と言っても実はWave-Robot-Sinatra-Templateって言うのがすでに存在するのでほとんどやることはないんだけど。ただ少し問題があって、このテンプレートとそれが使ってるSDKのJRubyラッパーは古いAPI用に作られているみたいで、そのままじゃ動かない。必要な修正は次の2つ。

1. Waveクライアントから送られてくるJSONを保持する環境変数が変わってるみたいなのでwar/WEB-INF/app.rbを以下のように修正。

修正前

post '/_wave/robot/jsonrpc' do
  logger.info "Input: " + request.env['rack.request.form_vars']
  json = JSON(request.env['rack.request.form_vars'])
  context = robot.execute_json_rpc!(json)
  output = AbstractRobot.serialize_context(context)
  output
end

修正後

post '/_wave/robot/jsonrpc' do
  input = request.env['rack.input'].read
  logger.info "Input: " + input
  json = JSON(input)
  context = robot.execute_json_rpc!(json)
  output = AbstractRobot.serialize_context(context)                                          
  output
end

2. 新しいAPIではロボットにバージョンが設定できるんだけど、JRubyラッパーはまだバージョンが設定できないので/waveapi/lib/abstract_robot.rbに処理を追加

追加1(AbstractRobotクラス宣言直後)

  @@version = ""
  def self.set_version(version)
    @@version = version
  end

追加2(AbstractRobot#capabilities内)

  def capabilities()
    """Return this robot's capabilities as an XML string."""
    lines = ['<w:capabilities>']
    lines+= events_handled.map{|e| '  <w:capability name="'+e+'"/>'}
    lines.push('</w:capabilities>')

    unless @@crons.empty?
      lines.push('<w:crons>')
      lines += @@crons.map{|job, timer| 
        '  <w:cron path="/_wave/robot/' + 
        job.to_s + 
        '" timerinseconds="' + timer.to_s + '"/>'
      }
      lines.push('</w:crons>')
    end

    # この行を追加
    lines.push("<w:version>#{@@version}</w:version>") unless @@version.empty?

    # ...snip...

以上の修正が終わったら/war/WEB-INF/robot.rbに好きなように処理を記述する。ちなみに私がやったことは入力をevalして返すだけ。週末はAPE移植やってて時間なかったもんで・・・。

require 'lib/waveapi/init'

class Robot < AbstractRobot
  set_name "WaveScriptTutor Bot"
  set_version "1.1.15"

  def BLIP_SUBMITTED(properties, context)
    wavelet = context.GetWavelets[0]
    blip = context.GetBlipById(wavelet.GetRootBlipId())
    script = blip.GetDocument.GetText
    logger.info "Script: #{script}"
    blip.GetDocument.SetText("#{script}\nResult:\n#{eval(script)}")
  end
end

実際に試してみるには普通のGAEアプリと同じように/war/WEB-INF/appengine-web.xmlを編集して

$ appcfg.sh update war

するだけ。

結果はこんな感じ。前半を入力してDoneするとResult:以下が追加された。

Robot APIJRubyラッパーは(Java版とは比較するまでもないとして)Python版と比べてもずっと楽だと思うのでRubyユーザーなら使ってみてもいいと思う。