2
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

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

この記事は 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 can follow users and tags
  2. you can stock useful information
  3. You can make editorial suggestions for articles
What you can do with signing up
2
Help us understand the problem. What are the problem?