Lazy listeners を使用して Lambda で Slack Bot を動かしてみた
Boltとは
Boltとは、Slack APIのフレームワークです。
PythonやJSなどでサポートしており、Slack APIの機能を楽に使用することできます。
Boltでは、DjangoやFlaskなどのWebサーバフレームワークとの互換性もあり、非常に扱いやすいです。
Adaptersとして紹介されているので興味がある方はそちらを参考にしてみてください。
Lambdaで動かすために
前述した通り、Boltでは、Webサーバ上で動作させることが一般的です。
というのも、Slack APIの一部では非同期処理が必要であるため、LambdaといったFaaSで動作させることは以前までは少し面倒でした。
しかし、Boltでは、Lambdaでも非同期処理を実現できるような Lazy listeners と呼ばれる機能が存在します。
Lazy listeners
Lazy listeners では、非同期処理を別のLambdaへと割り当てる機能を提供します。 非同期処理は、関数として同じファイル内に記述をし、その関数をLazy listenersとして指定するだけで、あとのLambda周りのことはBoltがやってくれます。 下記のコードですと、ackで指定したrespond_to_slack_within_3_seconds関数で一旦レスポンスを返し、時間のかかる処理run_long_processは別のLambda上で実行させます。
def respond_to_slack_within_3_seconds(body, ack): text = body.get("text") if text is None or len(text) == 0: ack(f":x: Usage: /start-process (description here)") else: ack(f"Accepted! (task: {body['text']})") import time def run_long_process(respond, body): time.sleep(5) # 5秒 sleepさせる respond(f"Completed! (task: {body['text']})") app.command("/start-process")( # ackの関数は、内部でackを使用する ack=respond_to_slack_within_3_seconds, # 非同期にしたい関数をlazyに指定する lazy=[run_long_process] )
実際にやってみる
実際に、Botのボタンクリックをトリガーとし、クリック直後と3秒経過後にメッセージが送信されるように作成してみます。
動作環境
以下の環境で行います。
Serverless Framework: 2.48.0 slack-bolt: 1.9.4
Slackの初期セットアップ
BotのAPIを発行します。まずは、以下のリンクを開き、Botを作成します。
Permissons
Botに持たせる権限を設定します。今回の場合は、チャンネルのメッセージのサブスクライブとメッセージその送信を行うので、channels:history
とgroups:history
を指定します。
権限付与後、Slackのワークスペースにインストールが求められるので、そのままインストールも行います。
Tokenの取得
Bot User OAuth Token
とSigning Secret
を取得し、メモしておきます。
Serverless アプリケーションの作成
slsコマンドでアプリケーションの作成を行います。
$ sls What do you want to make? AWS - Python - Starter What do you want to call this project? hello-lambda
また、プラグインとしてserverless-python-requirementsとserverless-dotenv-pluginを使用するので、こちらもインストールします。
$ sls plugin install -n serverless-python-requirements $ npm i -D serverless-dotenv-plugin
作成されたseverless.yamlをAPI GatewayとLambdaを呼び出せるように、以下のようにします。
service: hello-lambda frameworkVersion: '2' provider: name: aws runtime: python3.8 lambdaHashingVersion: 20201221 region: ap-northeast-1 iamRoleStatements: - Effect: "Allow" Action: - "lambda:InvokeFunction" - "lambda:GetFunction" Resource: "*" plugins: - serverless-python-requirements - serverless-dotenv-plugin functions: bolthello: handler: handler.handler events: - httpApi: path: / method: "*"
環境変数は、.envに準備します。
先ほどメモしたBot User OAuth Token
とSigning Secret
をそれぞれ、SLACK_BOT_TOKEN
、SLACK_SIGNING_SECRET
として指定します。
SLACK_BOT_TOKEN=xoxb-hogeguga111111111 SLACK_SIGNING_SECRET=abcd11223344eeff
使用するモジュールは、requirements.txtに記述します。
slack-bolt python-lambda python-dotenv
Pythonスクリプトの作成
今回は、helloというワードがチャンネルに流れた場合、反応し、ボタンを送信させます。 また、そのボタンをユーザがクリックした場合、クリックイベントを非同期で行うように実装しました。 非同期にしたい処理は、lazyにリスト形式で指定します。
import os from slack_bolt import App from slack_bolt.adapter.aws_lambda import SlackRequestHandler import time app = App(process_before_response=True, token=os.environ.get("SLACK_BOT_TOKEN"), signing_secret=os.environ.get("SLACK_SIGNING_SECRET") ) @app.message("hello") def message_hello(message, say): # イベントがトリガーされたチャンネルへ say() でメッセージを送信します say( blocks=[ { "type": "section", "text": {"type": "mrkdwn", "text": "こんにちは!"}, "accessory": { "type": "button", "text": {"type": "plain_text", "text":"ボタンを押す"}, "action_id": "button_click" } } ], text=f"Hey there <@{message['user']}>!" ) def action_button_click(body,ack,say): time.sleep(3) say("ボタンおして、3秒経過したよ!") def demo1(body,ack,say): say("ボタン押した!") ack("ボタン押した!") app.action("button_click")( ack=demo1, lazy=[action_button_click] ) def handler(event, context): slack_handler = SlackRequestHandler(app=app) return slack_handler.handle(event, context)
アプリケーションのデプロイ
下記コマンドを入力してデプロイを行なっていきます。
$ sls deploy
最後に、endpointsとしてURLが出力されます。
Slackにエンドポイントを追加
ここで、私はハマったのですが、2箇所エンドポイントを登録する必要があります。
Interactivity & ShortcutsのInteractivity
Interactivity & ShortcutsのInteractivityに先ほどのURLを追加します。
Event Subscriptions
続いて、Event Subscriptionsにも先ほどのURLを追加します。
さらに、メッセージを購読できるようにSubscribe to bot eventsにも権限を追加します。
検証
では、動くかどうかを検証してみます。メッセージでhelloと送信してみます。
すると、Botがメッセージとボタンが送信します。
このボタンを押してみると、「ボタン押した!」、そしてその3秒後に「ボタンおして、3秒経過したよ!」と正常に動作することが確認できます。
まとめ
Slack Botの非同期処理をLambdaで行ってみましたが、Slackの設定周りで苦労しました。
他にも、非同期処理ができるのであれば、WorkflowやShortcutなどもLambdaで動かせそうなので、またの機会にやってみたいです。