LoginSignup
2

More than 3 years have passed since last update.

Rubotyがサーバーレスで動くようにした

Posted at

この記事は PONOS Advent Calendar 2019 の25日目の記事です。
昨日は@kerimekaさんのゲームにおけるFirebase 活用例でした。

🎉🎁何とか全枠埋まりました!🎁🎉
記事を書いて頂いたみなさん、ありがとうございました!


Rubotyという、Rubyでチャットボットが作れるOSSがあります。
個人的に凄く好きなOSSで、何とか趣味なり仕事で使えないかと考えています。(趣味方面だと、@gymtterで一応使ってる。)

やりたかったこと

Rubotyはサーバー上に常駐して発言を待ち受け続けますが、これだと常に一定のリソースを占有します。(HerokuだったりEC2だったり。)
利用頻度の高いBotならまだしも、1時間に数回みたいな使い方だと非常に勿体ないので、サーバーレス化したいなと思いました。

会社ではチャットツールとしてChatworkを使っているんですが、Chatwork APIでは特定ルームに投稿があったときにWebhookを投げることができます。

参考: Chatwork APIドキュメント

このWebhookをトリガーにしてRubotyが動くようにすれば良さそうです。

やったこと

概要

こんな感じに動くようにしました。

  1. Chatworkで発言
  2. Chatwork APIのWebhook設定が発火
  3. API Gatewayのエンドポイントを叩く
  4. Lambdaに載っているRubotyを実行
  5. Chatworkに返事を投稿

Lambdaに載せているのは↓のリポジトリです。

honeniq/headless-ruboty

検証ではServerless Frameworkを使ってAWSの環境構築を省力化しているんですが、serverless.ymlの整理まで追いつかなかったので、まだリポジトリには載せていません。

Rubotyの挙動

Ruboty本来の動きはこんな感じです。

  1. 必要な諸々を読み込む
  2. 入力を待ち受ける
  3. Rubotyに対するコマンド(として認識できる文字列)を受け取る
  4. ハンドラーの中から該当するコマンドを探す
  5. 対応するアクションを実行する
  6. 2の待ち受け状態に戻る

今回の用途では、3から5だけ使えれば良さそうです。

pryで何が必要か調べる

pryでRubotyのメソッドを色々叩いてみて、必要なやつを探しました。

[1] pry(main)> require 'ruboty'
=> true

[2] pry(main)> bot = Ruboty::Robot.new
=> #<Ruboty::Robot:0x00007fc2b31c47d8 @options={}>

[3] pry(main)> bot.receive(body:'ruboty help')
ruboty /help( me)?(?: (?<filter>.+))?\z/i - Show this help message
ruboty /ping\z/i - Return PONG to PING
ruboty /who am i\?/i - Answer who you are
=> nil

初期状態のRubotyは標準出力に返事を返すので、この動作で合っていそうです。🎉

実装: handler.rb

Lambdaのエントリーポイントです。

ChatworkからAPI GatewayのエンドポイントにWebhookが飛んでくると、下記のtalkが呼ばれます。

handler.rb
def talk(event:, context:)
  message = JSON.parse(event['body'])['webhook_event']['body']
  @robot = Ruboty::Robot.new
  @robot.headless_run(message)

  {
    statusCode: 200,
    body: {
      message: 'done',
    }.to_json
  }
end

Lambdaから渡されるeventはHashで、event['body']にはChatworkが投げてきたWebhookのJSONがStringでそのまま入っています。

再掲: Chatwork APIドキュメント

sample_webhook
{
    "webhook_setting_id": "12345",
    "webhook_event_type": "mention_to_me",
    "webhook_event_time": 1498028130,
    "webhook_event":{
        "from_account_id": 123456,
        "to_account_id": 1484814,
        "room_id": 567890123,
        "message_id": "789012345",
        "body": "[To:1484814]おかずはなんですか?",
        "send_time": 1498028125,
        "update_time": 0
    }
}

最終的に取り出したいのはwebhook_eventの中のbodyなので、JSONをパースして対象の要素まで掘っています。

最後にステータスコード200を返しているのは、Chatwork APIの仕様に対応するためです。

実装: lib/ruboty/headless_run.rb

headless_run.rb
require 'ruboty'

module Ruboty
  class Robot
    def headless_run(message)
      dotenv
      bundle
      setup
      remember
      handle
      adapter
      receive(body: message)
    end
  end
end

Ruboty::Robotに新しいメソッドheadless_runを生やします。

このメソッドは、本家のRuboty::Robot#runでやっている処理から、待ち受け処理やデーモン化に必要な部分を除き、receiveを呼んでいるだけです。

結果

Chatworkのマイチャット上でRubotyに話しかけると、同じチャットに返事が返ってくるようになりました。🎉
(今回はRubotyも自分のアカウントで投稿しているので、ただの独り言みたいに見えるけど。)

image.png

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
2