この記事はニフティグループ Advent Calendar 2021の12/22分です。
はじめに
Slackでユーザーの何かしらの操作をトリガーに、何かの処理が可能なEvents APIを触ってみましたのでそのメモをまとめておきます。
Slack Events APIとは?
Slackで何かしらのアクションが発生したときに、事前に設定したエンドポイントに対して、通知をしてくれるAPI
Slack API公式
例…
- メンバーがワークスペースに参加したとき
- チャンネルが作られたとき
- リアクションが付けられたとき
など
今回は、サンプルとしてメンバーがワークスペースに参加したときに、メッセージを#generalに投稿させてみる。
(処理部分をカスタマイズすればいろいろ応用が効きます)
構成図
① Slack Events APIからメンバー参加時にデータが送られてくる
② 送られてきたデータをAPI Gatewayが受け取り、それをLambdaにわたす(以降、Lambda A
)
③ 受け取ったデータを必要な部分だけ抽出してSQSに渡す
④ Slack Event APIに200 OK
を返す
⑤ SQSからLambdaにデータが渡される(以降、Lambda B
)
⑥ Slack APIにメッセージを送信する
なんでSQSを挟むの??
Your app should respond to the event request with an HTTP 2xx within three seconds. If it does not, we'll consider the event delivery attempt failed. After a failure, we'll retry three times, backing off exponentially.
Maintain a response success rate of at least 5% of events per 60 minutes to prevent automatic disabling.
Respond to events with a HTTP 200 OK as soon as you can. Avoid actually processing and reacting to events within the same process. Implement a queue to handle inbound events after they are received.
Using the Slack Events API
Slackからのリクエストに対して3秒以内に200番台で応答しないといけないから。
なので、受信してから処理を行いレスポンスを返しているとタイムアウトで再送され、2重で処理をしてしまう可能性あり
(一応、再送されたデータにはX-Slack-Retry-Num
というヘッダーが付与されるので判別できなくはないです…)
手順
SQSでキューを作成する
Lambda同士をつなぐためのキューを作成する。
好きな名前をつけて、デフォルトで作成。
(ARNを後で利用するのでメモしておく)
Lambda Aを作成する
API Gatewayで受け取ったデータをSQSに入れて、200 OK
を返すだけのLambdaを作る。
関数名に好きな名前をつけて、ランタイムにPython3.9
を選択する。
ソースコード
import os
import boto3
import json
def lambda_handler(event, lambda_context):
request_body = json.loads(event['body'])
# Slack API設定時のURL検証に応答する
if request_body['type'] == 'url_verification':
return {
'statusCode': 200,
'body': json.dumps(
{
'challenge': request_body['challenge']
}
)
}
# Slack APIから来たイベントか検証する
if request_body['token'] != os.environ.get('VERIFICATION_TOKEN'):
return {
'statusCode': 400,
'body': json.dumps({
'msg': 'inviled token'
})
}
# ゲストとボットは除く
if not (request_body['event']['user']['is_restricted'] or request_body['event']['user']['is_bot']):
QUEUE_NAME = os.environ.get('QUEUE_NAME')
sqs = boto3.resource('sqs')
queue = sqs.get_queue_by_name(QueueName=QUEUE_NAME)
# 参加メンバーのIDと名前をSQSに送る
msg_dict = {
'id': {
'StringValue': request_body['event']['user']['id'],
'DataType': 'String',
},
'name': {
'StringValue': request_body['event']['user']['profile']['real_name'],
'DataType': 'String',
},
}
queue.send_message(MessageBody='NewMemberJoin!', MessageAttributes=msg_dict)
# Slack APIに200 OKを返却
return {
'statusCode': 200,
'body': json.dumps({
'msg': 'ok'
})
}
Lambda Aの実行ロールにSQSの書き込み権限を適用する
作成したLambda Aの「設定」 → 「アクセス権限」 → 「ロール名」をクリックで、このLambdaに紐付いているIAM roleにアクセス。
IAMの画面から「インラインポリシーを追加」 → 「JSON」タブをクリックし、下記の内容をコピー。
(Resource
の値には、先程作成したSQSのARNを記入)
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"sqs:SendMessage",
"sqs:GetQueueUrl"
],
"Resource": "[さっき作成したSQSのARN]"
}
]
}
記入したら、「ポリシーの確認」をクリック。
好きな名前をつけて「ポリシーの作成」をクリックすれば、Lambda AにSQSの書き込み権限が付与完了。
API GatewayでエンドポイントURLを払い出す
API Gatewayのページから、HTTP API
を「構築」。
「統合」では「Lambda」を選択し、先程作成したLambda Aを選択。
これで、このエンドポイントにデータが来ると、Lambda Aに渡され、実行されるようになる。
問題なければ「確認して作成」 → 「作成」をクリック。
これでエンドポイントが作成完了。
「ルート」にあるパスを結合したものが、エンドポイントURL
(画像の場合だと、https://*****.amazonaws.com/SlackEventsApiHandler
)
あとで利用するのでメモしておく。
Slack APIの設定をする
Slack APIのページに行き、「Create an app」をクリック
インストール先のワークスペースを選択したら、次のページで以下のYAMLを貼り付ける。
request_url
には、先程作成したエンドポイントURLを、name
、description
、desplay_name
には好きな値を入力する。
(ここで入力エラーが出たら、パラメーターに日本語ではなく、アルファベットのみを入力してみると解決する…)
_metadata:
major_version: 1
minor_version: 1
display_information:
name: [アプリの名前]
description: [アプリの説明]
features:
app_home:
home_tab_enabled: true
bot_user:
display_name: [Slack上での表示名]
always_online: true
oauth_config:
scopes:
bot:
- incoming-webhook
- users:read
settings:
event_subscriptions:
request_url: [先程払い出したエンドポイントURL]
bot_events:
- team_join
org_deploy_enabled: false
socket_mode_enabled: false
is_hosted: false
エラーが出なければnext
で確認をした上で作成。
Slack Appの画面に遷移したら、「Install to Workspace」をクリックし、ワークスペースにインストールしておく。
インストール時にメッセージの投稿先が聞かれるので、メンバーが参加したときにメッセージを送信したいチャンネルを指定。
その後、Incoming Webhookの画面からWebhook URLをメモ
加えて、Event Subscriptionsの画面で、先ほどをエンドポイントURLを入力し、Verified
が表示されることを確認する。これで、Slackでイベントが発生したら、API Gateway経由でLambda Aが実行されるように。
(manifestのrequest_url
が適用されなかったのは謎…)
Lambda Aに環境変数を設定する
今設定したアプリの認証用のTokenをLambda Aの環境変数に設定
環境変数に設定するもの
Key | Value |
---|---|
VERIFICATION_TOKEN |
先程払い出したSlack APIのToken |
QUEUE_NAME |
作成したSQSのキュー名 |
Lambda Bを作成する
SQSからデータを受け取り、Slackにメッセージを送信する処理。
(ここの処理を変えれば、自由にイベントに対応する処理をカスタマイズできる)
ただし、このソースではLambdaで利用できないrequests
ライブラリが必要のため、一旦ローカルでソースとライブラリを用意して、zipで固めてアップロードする。
ソースコード
import requests
import os
def lambda_handler(event, hambda_context):
for record in event['Records']:
msg_json = record['messageAttributes']
name = msg_json['name']['stringValue']
id = msg_json['id']['stringValue']
requests.post(os.environ.get('WEBHOOK_URL'), json={
'text': f'{name}さん(<@{id}>)がメンバーとして参加しました:+1:'
})
requests
をローカルに保存する
% pip install requests -t .
ディレクトリはこんな感じに。これをzipにして、Lambda Bにアップロード
環境変数に設定する
Key | Value |
---|---|
WEBHOOK_URL |
先程払い出したIncoming Webhook URL |
Lambda Bの実行ロールに下記のポリシーをインラインで追加
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"sqs:ReceiveMessage",
"sqs:GetQueueAttributes",
"sqs:DeleteMessage"
],
"Resource": "[SQSのARN]"
}
]
}
SQSをトリガーとして起動するように設定
動作確認
メンバーが参加すると、メッセージが指定したチャンネルに飛んでくる
その他
Lambda AのSlack APIから来たデータか確認する部分は、実は新しい方法がある…