AWS Lambda + API GatewayでSlackのSlash commandを作る(python編)

Slackでスッキリすの結果を見る需要があったので、Slashコマンドを初めて使ってみました。
またAWSも触ってみたかったので、AWS Educateに登録してLambdaに処理させるようにしました。
今回はSlash commandをLambdaランタイム上のPythonで実行する方法を記録します。

はじめに

今回作ったコードはGitHubに保存しているので、併せて参照してください。
ちなみにスッキリすのスクレイピングの話はしません。

構成

Slackでslash commandを実行、API Gatewayを通じてLambdaに処理が渡され、スッキリすのスクレイピングをし、その結果を返す構成です。

準備

Slack slash commandの入出力

Slash commandを打った時にPOSTされるパラメータは以下のドキュメントに掲載されています。
https://api.slack.com/slash-commands#app_command_handling

ちなみに掲載時点ではパラメータ例は以下のようになっています。

token=gIkuvaNzQIHg97ATvDxqgjtO
&team_id=T0001
&team_domain=example
&enterprise_id=E0001
&enterprise_name=Globular%20Construct%20Inc
&channel_id=C2147483705
&channel_name=test
&user_id=U2147483697
&user_name=Steve
&command=/weather
&text=94070
&response_url=https://hooks.slack.com/commands/1234/5678
&trigger_id=13345224609.738474920.8088930838d88f008e0

これらがContent-Type: application/x-www-form-urlencodedで送られます。

出力はtextあるいはjsonで構成します。
特にこだわりがなければtextだけでいいですが、jsonでリッチなメッセージを送ることもできます。

以下のドキュメントに簡単な説明が書いてあります。
https://api.slack.com/slash-commands#responding_immediate_response
通常slash commandの応答はcommandを打った人にしか表示されないため、全員に見えるように応答する場合は"response_type": "in_channel"というkey-valueをつけたjsonにする必要があります。

AWS API Gateway

AWSのAPI GatewayとLambdaの結合部分は統合プロキシと呼ばれています。
統合プロキシの入力(API Gateway → Lambda)と出力(API Gateway ← Lambda)は以下のドキュメントに掲載されています。
https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-create-api-as-simple-proxy

掲載時点では以下のようになっています。
(2019/11/10追記:出力フォーマットが入力フォーマットと同一になっていた誤りを修正。)

{
    "resource": "Resource path",
    "path": "Path parameter",
    "httpMethod": "Incoming request's method name"
    "headers": {String containing incoming request headers}
    "multiValueHeaders": {List of strings containing incoming request headers}
    "queryStringParameters": {query string parameters }
    "multiValueQueryStringParameters": {List of query string parameters}
    "pathParameters":  {path parameters}
    "stageVariables": {Applicable stage variables}
    "requestContext": {Request context, including authorizer-returned key-value pairs}
    "body": "A JSON string of the request payload."
    "isBase64Encoded": "A boolean flag to indicate if the applicable request payload is Base64-encode"
}
{
    "isBase64Encoded": true|false,
    "statusCode": httpStatusCode,
    "headers": { "headerName": "headerValue", ... },
    "multiValueHeaders": { "headerName": ["headerValue", "headerValue2", ...], ... },
    "body": "..."
}

ちなみにこの出力は、Pythonのdictionary型でもよいことを確認しました。

コード作成

今回はPythonを用います。
理由はよく使用しているスクレイピングライブラリのBeautifulSoupが使えたからです。

Lambdaから呼び出された際に実行する関数はハンドラと呼ばれ、2引数関数で実装します。
(特にファイル名や関数名は問わない、自由に設定できる。)

def lambda_handler(event, context):

前述のAPI Gatewayの入力はevent(第1引数)に渡されます。
第2引数のcontextはLambdaを実行している環境の情報が与えられます(今回は触れない)。

詳しいことはドキュメントを参照してください。
PythonのLambdaハンドラ:
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/python-programming-model-handler-types.html
第2引数のLambdaコンテキスト:
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/python-context-object.html

Slackのコマンドの引数はtextパラメータに保存されるので、以下のように抜き出します。

from urilib.parse import parse_qs

def lambda_handler(event, context):
    input_text = parse_qs(event['body'])['text'][0].rstrip()

ここでrstrip()を使っているのは、パラメータの末尾に改行が含まれているためです。

また、返り値をAPI Gateway、Slash Commandそれぞれのフォーマットに合わせる必要があるため、最終的に以下のような関数になります。

from urilib.parse import parse_qs
import json

def lambda_handler(event, context):
    input_text = parse_qs(event['body'])['text'][0].rstrip()
    ...
    return {
        "isBase64Encoded": false,
        "statusCode": 200,
        "headers": {},
        "body": json.dumps({
            "response_type": "in_channel",
            "text": ***OUTPUT***
        })
    }

あとは好きな処理を加えて煮るなり焼くなり好きにします。

コードをzip保存

LambdaのコードはWebコンソール上のエディタで作成することもできますが、zipアップロードすることもできます。
ここではpipでインストールしたモジュールも入れたいため、zipアップロードを使用します。

まずはzipを作る前に、ソースと同じディレクトリにモジュールを落とします。

pip install -r requirements.txt -t .
#あるいは
pip install <package> -t .

続いて、ソースとモジュールを同時にzipで固めます。

zip -r source.zip *

これでpipモジュールを使っていても、全く同じコードで走らせることができます。

デプロイ

AWS

Lambdaのコンソールを開き、「関数の作成」を選択。
編集画面に移ったら、「関数コード」ペインにスクロールし、以下のように設定して保存。

  • コードエントリタイプ:.zipファイルをアップロード
  • ランタイム:指定の言語
  • ハンドラ:ファイル名.関数名(sukkirisu.pylambda_handler()ならsukkirisu.lambda_handler

続いて上の「Designer」ペインにスクロールし、「+トリガーを追加」を選びます。

APIは「新規APIの作成」を選びます。
セキュリティは「オープン」とします。本当は設定したいのですが、Slash Commandのリクエストがユーザ指定のリクエストに対応していないので、どうしてもセキュリティ設定をしたい場合はPOSTリクエスト内容を見るしか現状ないです。

これで出来上がるエンドポイントURLを控えておきます。これでAWS側の設定は完了です。

Slack

Slack APIのapps(https://api.slack.com/apps)でアプリを作成します。
作成したら、「Features」メニューの「Slash Commands」を選びます。

「Create New Command」を選び、新規コマンド作成画面へ。
ここでRequest URLに先ほど控えたAPI GatewayのURLを入れます。

これにて完成です。コマンドを叩いたら、レスポンスが返ってくるか確認します。

参考資料

https://qiita.com/yaquality/items/6516a17ec01245e4321d

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です