LoginSignup
9
1

More than 1 year has passed since last update.

【Slack】Events APIとAWSでトリガー処理してみた

Last updated at Posted at 2021-12-21

この記事はニフティグループ Advent Calendar 2021の12/22分です。

はじめに

Slackでユーザーの何かしらの操作をトリガーに、何かの処理が可能なEvents APIを触ってみましたのでそのメモをまとめておきます。

Slack Events APIとは?

Slackで何かしらのアクションが発生したときに、事前に設定したエンドポイントに対して、通知をしてくれるAPI
Slack API公式

例…
- メンバーがワークスペースに参加したとき
- チャンネルが作られたとき
- リアクションが付けられたとき
など

今回は、サンプルとしてメンバーがワークスペースに参加したときに、メッセージを#generalに投稿させてみる。
(処理部分をカスタマイズすればいろいろ応用が効きます)

構成図

構成図.png

① 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を後で利用するのでメモしておく)
スクリーンショット 2021-12-07 19.41.51.png

Lambda Aを作成する

API Gatewayで受け取ったデータをSQSに入れて、200 OKを返すだけのLambdaを作る。

関数名に好きな名前をつけて、ランタイムにPython3.9を選択する。

ソースコード

lambda_function.py
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にアクセス。
スクリーンショット 2021-12-07 19.47.13.png

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を「構築」。
スクリーンショット 2021-12-07 19.55.45.png

「統合」では「Lambda」を選択し、先程作成したLambda Aを選択。
これで、このエンドポイントにデータが来ると、Lambda Aに渡され、実行されるようになる。

問題なければ「確認して作成」 → 「作成」をクリック。
これでエンドポイントが作成完了。

作成したAPIのURLと…
スクリーンショット 2021-12-07 19.59.56.png

「ルート」にあるパスを結合したものが、エンドポイントURL
スクリーンショット 2021-12-07 20.00.17.png
(画像の場合だと、https://*****.amazonaws.com/SlackEventsApiHandler

あとで利用するのでメモしておく。

Slack APIの設定をする

Slack APIのページに行き、「Create an app」をクリック

今回は最近登場したmanifestを利用して作成してみる。
スクリーンショット 2021-12-07 20.08.29.png

インストール先のワークスペースを選択したら、次のページで以下のYAMLを貼り付ける。
request_urlには、先程作成したエンドポイントURLを、namedescriptiondesplay_nameには好きな値を入力する。
(ここで入力エラーが出たら、パラメーターに日本語ではなく、アルファベットのみを入力してみると解決する…)

manifest.yml
 _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」をクリックし、ワークスペースにインストールしておく。
スクリーンショット 2021-12-07 20.13.08.png

インストール時にメッセージの投稿先が聞かれるので、メンバーが参加したときにメッセージを送信したいチャンネルを指定。
スクリーンショット 2021-12-07 20.13.36.png

その後、Incoming Webhookの画面からWebhook URLをメモ
スクリーンショット 2021-12-07 20.19.21.png

加えて、Event Subscriptionsの画面で、先ほどをエンドポイントURLを入力し、Verifiedが表示されることを確認する。これで、Slackでイベントが発生したら、API Gateway経由でLambda Aが実行されるように。
スクリーンショット 2021-12-07 20.20.49.png

(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で固めてアップロードする。

ソースコード

lambda_handler.py
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にアップロード
スクリーンショット 2021-12-16 22.21.21.png

環境変数に設定する

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をトリガーとして起動するように設定

スクリーンショット 2021-12-16 22.32.28.png

動作確認

メンバーが参加すると、メッセージが指定したチャンネルに飛んでくる
スクリーンショット 2021-12-19 22.16.26.png

その他

Lambda AのSlack APIから来たデータか確認する部分は、実は新しい方法がある…

9
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
9
1