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

AWS Lambda を使って Slack ボット (命名: Lambot [ランボー]) を低予算で作ろうじゃないか

More than 5 years have passed since last update.

はじめに

Slack用ボットの定番は Heroku+Hubot だと思いますが、

  • もっと簡単、シンプルに
  • よりElastic (負荷の増減に柔軟)に
  • かつ、低予算 (サーバーレス) で

運用したいので、AWS Lambda 上に、ライブラリ(Hubot)を使わないで構築します。

システム構成は以下のようになります。

スクリーンショット 2015-11-01 12.44.16.png

Slack→Lambda連携では、Content-Type について

  • Slack「Outgoing WebHooks」出力は、 application/x-www-form-urlencoded
  • AWS「Lambda」入力は、application/json

なので、API Gateway での Content-Typeの変換処理がポイントになります。

以下、順番に作成していきます。

AWS側の設定

最初はLambda関数から。

Step 1: Select blueprint

  • 「microservice-http-endpoint」を選択。(これはらくちんだ!)

スクリーンショット_2015-11-01_13_17_18.png

Step 2: Configure function

  • Name ですが「Lambot(ランボー)」と名づけました。(Lambda+Bot)

スクリーンショット_2015-11-01_13_24_49.png

  • Lambda関数は、「Edit code inline」で以下を記述。(→参考 1)
exports.handler = function(event, context) {

  var text = [
    event.token,
    event.team_id,
    event.team_domain,
    event.channel_id,
    event.channel_name,
    event.timestamp,
    event.user_id,
    event.user_name,
    event.trigger_word
  ].join("\n");

  text = ["```" + text + "```", event.text].join("\n");

  context.done(null, {text: text});

};
  • Role は「Basic execution role」で。
  • Memory は最小(128MB) でよい。(→参考 2)

スクリーンショット 2015-11-01 13.24.02.png

Step 3: Configure endpoints

  • API Name は「Lambot」
  • Method は「POST」
  • Security は「Open」

スクリーンショット 2015-11-01 14.21.57.png

Lamba 関数が作成できたら、以下の画面になります。
「API endpoint URL」をメモしておいて下さい。(後でSlack側に設定します。)
「POST」をクリックして、API Gateway に移動します。
スクリーンショット_2015-11-01_15_12_19.png

以下の API Gateway に移動したら、「Integration Request」をクリック。

スクリーンショット_2015-11-01_1_38_36.png

さらに、「Mapping Template」をクリック。

スクリーンショット_2015-11-01_14_33_31.png

ここが一番分かりづらいですが、要は「application/x-www-form-urlencoded」の時は json 型に変換するマッピングを定義します。
1.「Add mapping template」をクリック
2.「application/x-www-form-urlencoded」を入力して、すぐ右にあるチェックをクリック
3. 保存されたら、「application/x-www-form-urlencoded」をクリック
4.「Input passthrough」の右の鉛筆をクリック。
5.「Input passthrough」を「Mapping template」に変更。
6. 「Template」に以下を記載。
7. 保存されていることを確認。

スクリーンショット_2015-11-01_14_37_01.png

Templateコード (→参考 3)

## convert HTML POST data or HTTP GET query string to JSON

## get the raw post data from the AWS built-in variable and give it a nicer name
#if ($context.httpMethod == "POST")
 #set($rawAPIData = $input.path('$'))
#elseif ($context.httpMethod == "GET")
 #set($rawAPIData = $input.params().querystring)
 #set($rawAPIData = $rawAPIData.toString())
 #set($rawAPIDataLength = $rawAPIData.length() - 1)
 #set($rawAPIData = $rawAPIData.substring(1, $rawAPIDataLength))
 #set($rawAPIData = $rawAPIData.replace(", ", "&"))
#else
 #set($rawAPIData = "")
#end

## first we get the number of "&" in the string, this tells us if there is more than one key value pair
#set($countAmpersands = $rawAPIData.length() - $rawAPIData.replace("&", "").length())

## if there are no "&" at all then we have only one key value pair.
## we append an ampersand to the string so that we can tokenise it the same way as multiple kv pairs.
## the "empty" kv pair to the right of the ampersand will be ignored anyway.
#if ($countAmpersands == 0)
 #set($rawPostData = $rawAPIData + "&")
#end

## now we tokenise using the ampersand(s)
#set($tokenisedAmpersand = $rawAPIData.split("&"))

## we set up a variable to hold the valid key value pairs
#set($tokenisedEquals = [])

## now we set up a loop to find the valid key value pairs, which must contain only one "="
#foreach( $kvPair in $tokenisedAmpersand )
 #set($countEquals = $kvPair.length() - $kvPair.replace("=", "").length())
 #if ($countEquals == 1)
  #set($kvTokenised = $kvPair.split("="))
  #if ($kvTokenised[0].length() > 0)
   ## we found a valid key value pair. add it to the list.
   #set($devNull = $tokenisedEquals.add($kvPair))
  #end
 #end
#end

## next we set up our loop inside the output structure "{" and "}"
{
#foreach( $kvPair in $tokenisedEquals )
  ## finally we output the JSON for this pair and append a comma if this isn't the last pair
  #set($kvTokenised = $kvPair.split("="))
 "$util.urlDecode($kvTokenised[0])" : #if($kvTokenised[1].length() > 0)"$util.urlDecode($kvTokenised[1])"#{else}""#end#if( $foreach.hasNext ),#end
#end
}

最後に、「Deploy API」をクリックして、ステージ「prod」をデプロイしてください。
スクリーンショット_2015-11-01_15_06_11.png
スクリーンショット_2015-11-01_15_07_41.png

これで AWS 側は完成。

Slack側の設定

Step 1: Outgoing WebHooks

チームのIntegrations 設定で、「Outgoing WebHooks」を選択してください。

スクリーンショット_2015-11-01_15_18_54.png

以下は、「Outgoing WebHook」の設定になります。
- Channelを「Any」にしたい場合は、「Trigger Word」を指定する必要があります。
- 今回は全ての発言をトリガーしたいので、「#general」チャンネルに限定して、「Trigger Word」は「(なし)」にしています。

スクリーンショット_2015-11-01_15_24_17.png

URLは、先ほどメモした API Gateway のリソース URL を記述してください。
スクリーンショット_2015-11-01_15_24_55.png
Name と Icon は、お好みで。
スクリーンショット_2015-11-01_15_25_53.png

「Save Settings」を押下すれば、Slack側の設定は完成です。

確認

Slack画面で発言してみます。おぉ、Lambotから返事がきました。
スクリーンショット_2015-11-01_15_35_40.png

今後の展開

  • Lambda関数の内部を充実させて、何かコマンドが実行できるようにします。
  • LambdaはSQSをトリガーにできない(2015/11/01)です。もしできるようになれば、非同期処理的なコマンドも処理できるようになるでしょう。

参考

参考 1

  • Lambda関数は、今回はインライン編集ですませましたが、npmモジュールが必要な場合は package.json と node_modules が含まれたコード一式をzipで固めてアップロードします。

参考 2 (AWS Lambda 費用)

リクエストのうち毎月最初の 1,000,000 件は無料

メモリ128MB(最小)の場合

1 か月の無料利用枠の秒数 100 ミリ秒単位の価格 (USD)
3,200,000 0.000000208

費用について詳しくは以下を参照
https://aws.amazon.com/jp/lambda/pricing/

参考 3 (x-www-form-urlencoded を Lambda で処理するには)

Special Thanks!!!!
- https://forums.aws.amazon.com/thread.jspa?messageID=673012&tstart=0#673012

exabugs
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