LoginSignup
12
1

More than 5 years have passed since last update.

AWS Lambdaでirb風のSlackチャンネルを作る

Last updated at Posted at 2018-12-05

こんにちは。PORT株式会社の18卒エンジニアの@rnittaです。
業務ではサーバー/フロント/iOSの開発をしています。

1週間ほど前にAWS LambdaがRubyをサポート(&あらゆる言語対応)というアナウンスがありましたので、
今回はFaaStRubyへの追悼の意を込めて、「LambdaでRuby」ネタを書きます。

今回作るもの

Slackのチャンネルをirb風にします。
irbとはRubyのREPL(Read-Eval-Print Loop)ですが、今回は「Read-Eval-Print」するだけのものを作ります。
そういった意味でirb"風"です。

今回のゴール:
スクリーンショット 2018-12-05 20.20.33.png

こういった、SlackにRubyのコードを送信すると
Rubyのコードとして解釈して返してくれるボットを作り、
チャンネルをirb化させます。

以下手順です。

1. Slackにチャンネルを作成

irb化させるチャンネルを作ってください。

2. 発信Webフックを追加

Slackをカスタマイズ > App管理 > カスタムインテグレーション > 発信 Webフック(Outgoing Webhook) > 設定を追加
するとSlackへの投稿をフックに発動するボットを追加できます。
追加したら各項目を適切に設定してください。

「チャンネル」は先程作ったチャンネルに、
「引き金となる言葉」は空のままに、
「URL」も一旦空にしておきます(あとでAPI GatewayのURLを入れます)。

3. AWS Lambdaで関数を作成

「ランタイム」はRubyを選択し、適当な名前をつけて作成します。
スクリーンショット 2018-12-05 20.33.43.png

関数の編集画面に入ったら、
Designerから高輪API Gatewayを追加します。
API Gatewayの設定はあとでします。
スクリーンショット 2018-12-05 20.35.54.png

発信Webフックで飛んできたリクエストの本文をコードとして実行して、
json形式で返す処理を書きます。
Hashを返せばjsonで出力してくれます。
リクエストやレスポンスの形式は発信Webフックの「発信ペイロードとレスポンス」を参考にします。

そしてインラインでコード書きます。

require 'uri'
require 'cgi'

# 出力先をstringオブジェクトにする
require 'stringio'
$stdout = StringIO.new

def execute(code)
  # 文字列をrubyコードとして実行して返す
  eval(code, binding, 'port_inc.rb')
rescue => error
  error
end

# このメソッドが最初に呼ばれる eventにはリクエストの本文がHashで入っている
def lambda_handler(event:, context:)
  # tokenが一致しなければ弾く 本来はオーソライザでやったほうがよさそう
  return unless event['token'] == ENV['SLACK_TOKEN']
  # チャンネルにはこのボット自体の投稿をフックにしてリクエストが飛ぶので、ボットの投稿は無視する
  return if event['user_name'] == 'slackbot'

  # デコードする
  decodedcode =  CGI.unescapeHTML(URI.decode_www_form_component(event['text']).tr(%q(“”‘’), %q(""'')))
  # slackに投稿する本文を生成する
  output = "#{$stdout.string}=> #{execute(decodedcode).to_s}"
  # なぜかグローバル変数がリクエストをまたいで共有されるので空にする
  $stdout.reopen('')
  { text: output, username: 'ruby' }
rescue => error
  $stdout.reopen('')
  { text: 'エラー: ' + error.to_s, username: 'ruby' }
end

注意すべき点としては、

やたらめったら誰彼構わず世界中から叩けると困る気がするので、
(ハードコードしてもいいですが) ENV['SLACK_TOKEN']に先程作ったボットのトークンを入れておき、リクエスト毎にそのトークンが含まれるか検証します。
スクリーンショット 2018-12-05 20.52.05.png
↑環境変数はコードの真下のここで設定します。

(リクエスト元をIPで絞る事はできないようです。)

注意すべき点②は、

slackから飛んでくるリクエスト本文は入力したものとは多少異なります。

まずapplication/x-www-form-urlencodedという形式でエンコードされています。
たとえば半角スペースは +に置き換わってたりします。
これを URI.decode_www_form_componentでデコードし、
また、実体参照でエスケープされているので CGI.unescapeHTMLで戻してあげます。

また、Slackクライアントの仕様で(少なくともmac版は ?)クォーテーションが全角のものに置換されるので、
String#trで置換します。

注意すべき点③は、

発信Webフックにおいて、
slackに投稿されたメッセージの投稿者の名前のキーは user_nameですが、
それに対する返信の投稿者名のキーは usernameで、
その値は bot_nameとして採用されるという正気とは思えない仕様です。
大変混乱しました。

ボットからの投稿の user_nameは常に slackbotなので、
return if event['user_name'] == 'slackbot'
無限ループを回避できます。

4. API Gatewayを設定してデプロイする

先程関数に紐づけたAPI Gatewayを「Amazon API Gateway コンソール」から編集します。
適当にPOSTアクションつくって、
マッピングテンプレートをいい感じにします。
application/x-www-form-urlencoded

#set($parameters = $input.path('$').split("&"))
{
#foreach( $keyValue in $parameters )
 #set($data = $keyValue.split("="))
 "$data[0]": "$data[1]" #if( $foreach.hasNext ),#end
#end
}

application/x-www-form-urlencoded形式のリクエストをjson形式にマッピングしています。
#が頭にある行が処理で、ない行が出力だそうです。VTL。

マッピングテンプレートを設定したら、
スクリーンショット 2018-12-05 21.24.16.png
アクションからデプロイすればapiのURLが得られます。

5. 発信Webフックの「URL」にAPIのURLを設定

します。

スクリーンショット 2018-12-05 21.27.38.png

6. irb風チャンネル完成!

スクリーンショット 2018-12-05 21.28.16.png

チャンネルに適当なコードを送信して返ってきたら完了です。

Slack_irbはこんなことに使えます

素数判定したり

スクリーンショット 2018-12-05 21.40.14.png

コードと実行結果が他のユーザーと共有できるので、
公正な発表順の抽選に使ったり

スクリーンショット 2018-12-05 21.34.54.png

世界の国々の略称を視覚的に確認したり

スクリーンショット 2018-12-05 21.43.40.png

できます!

まとめ

実用上の話をすれば、irb風のSlackチャンネルの実用性は0ですが、
こういうものを作ってみてAWSのサービスの使い方を覚えたり試行錯誤することが大事なんじゃないでしょうか(適当)


最後になりますが、PORT株式会社では自社サービスを支えてくれる優秀なRubyエンジニアを募集しています(Rubyエンジニア以外も)。
もくもく会も行なっていますので、ぜひ一緒にもくもくしましょう!
PORTもくもく会

PORT Advent Calendarはまだまだ続きます。乞うご期待!

12
1
0

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
  3. You can use dark theme
What you can do with signing up
12
1