Help us understand the problem. What is going on with this article?

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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした