[Slack] 特定チャンネルの特定キーワードに反応して、自動リアクションするSlackアプリ(Bot)を作る
Slackで特定のキーワードを含むメッセージに対して、自動でリアクションを付けたいことってありますよね。というわけで、実際にやってみました。
おすすめの方
- Slackで特定チャンネルのメッセージで動くSlackアプリ(Bot)を作成したい方
- Slackで自動でリアクションを付与したい方
- 上記の仕組みをサーバーレスで作成したい方
ざっくり構成
SlackのEvents APIで特定チャンネル(Slackアプリが追加されたチャンネル)のメッセージを受信し、reactions.addでリアクションを付与します。
自動リアクションBotを作成する(準備編)
Slackアプリ作成時にWebAPIを指定するため、最初にVerify用のAPIを作成します。
SAM Init
sam init \ --runtime python3.8 \ --name Slack-Team-IoT-Reaction-Bot \ --app-template hello-world \ --package-type Zip
SAMテンプレート
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: Slack-Team-IoT-Reaction-Bot Resources: SlackChannelSubscribeFunction: Type: AWS::Serverless::Function Properties: CodeUri: src/slack_channel_subscribe/ Handler: app.lambda_handler Runtime: python3.8 Timeout: 10 Events: Message: Type: Api Properties: Path: /message Method: post SlackChannelSubscribeFunctionLogGroup: Type: AWS::Logs::LogGroup Properties: LogGroupName: !Sub /aws/lambda/${SlackChannelSubscribeFunction} Outputs: SlackMessageApi: Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/message/"
Lambdaコード
APIのURL検証が必要なので、Event APIのドキュメントに従って、受け取ったパラメータのchallengeを返しています。
import json import logging logger = logging.getLogger() logger.setLevel(logging.INFO) def lambda_handler(event, context): logger.info(event['body']) body = json.loads(event['body']) return { 'statusCode': 200, 'body': json.dumps( {'challenge': body['challenge']}, ), }
デプロイ
sam build --use-container sam package \ --output-template-file packaged.yaml \ --s3-bucket cm-fujii.genki-deploy sam deploy \ --template-file packaged.yaml \ --stack-name Slack-Team-IoT-Reaction-Bot-Stack \ --s3-bucket cm-fujii.genki-deploy \ --capabilities CAPABILITY_NAMED_IAM \ --no-fail-on-empty-changeset
APIのエンドポイントを取得
下記コマンドでAPIのエンドポイントを取得します。Slackアプリ作成時に利用するため、メモしておきます。
aws cloudformation describe-stacks \ --stack-name Slack-Team-IoT-Reaction-Bot-Stack \ --query 'Stacks[].Outputs'
Slackアプリの作成
新規作成
Slack Appにアクセスして、アプリを新規作成します。
Slackアプリの設定
Event Subscriptionsの設定
Basic Informationにある「Event Subscriptions」を選択します。
「Request URL」にさきほどデプロイしたURLを入力し、Verify OK
になることを確認します。
続けて、Event APIにメッセージのRead権限として、message.channels
を付与します。
忘れずにSaveします。
OAuth & Permissionsの設定
「Scopes」でreactions:write
を付与します。
Slackアプリをワークスペースにインストールする
「Install to Workspace」を選択してインストールします。
インストール後、Bot User OAuth Token
が表示されるので、メモしておきます。これは、リアクション付与時に使用します。
任意のチャンネルにSlackアプリを追加する
Slackで任意のチャンネルを選択し、さきほどインストールしたSlackアプリを追加します。
「Slackアプリを追加したチャンネルのメッセージ」がデプロイしたAPIに渡されます。
自動リアクションBotを作成する(リアクション編)
SSMパラメータストアにトークンを保存する
SecureStringとして保存しています。
aws ssm put-parameter \ --type 'SecureString' \ --name '/Slack/Toekn/Team-IoT-Reaction-Bot' \ --value 'xoxb-xxx-yyy-zzz'
SAMテンプレート
SSMパラメータストアのRead権限やSSMパラメータストアに保存したトークンのKey名を環境変に設定しています。
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: Slack-Team-IoT-Reaction-Bot Parameters: SlackAppTokenKey: Type: String Resources: SlackChannelSubscribeFunction: Type: AWS::Serverless::Function Properties: CodeUri: src/slack_channel_subscribe/ Handler: app.lambda_handler Runtime: python3.8 Policies: - arn:aws:iam::aws:policy/AmazonSSMReadOnlyAccess Environment: Variables: SLACK_APP_TOKEN_KEY: !Ref SlackAppTokenKey Timeout: 10 Events: Message: Type: Api Properties: Path: /message Method: post SlackChannelSubscribeFunctionLogGroup: Type: AWS::Logs::LogGroup Properties: LogGroupName: !Sub /aws/lambda/${SlackChannelSubscribeFunction} Outputs: SlackMessageApi: Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/message/"
Lambdaコード
Lambdaコードを下記に変更します。メッセージの内容を確認し、いいね
が含まれている場合に、:good:
のリアクションを付与します。
Verifying requests from Slack | Slack
Lambda上で動くPythonにてSlack Appからのリクエストを検証する
slackapi bolt-python: A framework to build Slack apps using Python
import json import logging import os import boto3 import requests logger = logging.getLogger() logger.setLevel(logging.INFO) SLACK_APP_TOKEN_KEY = os.environ['SLACK_APP_TOKEN_KEY'] ssm = boto3.client('ssm') def lambda_handler(event: dict, context: dict): logger.info(event['body']) body = json.loads(event['body']) if is_reaction_message(body) is False: return token = get_token() reaction_slack(body, token, 'good') return { 'statusCode': 200, } def is_reaction_message(body: dict) -> bool: return 'いいね' in body['event']['text'] def get_token() -> str: res = ssm.get_parameter( Name=SLACK_APP_TOKEN_KEY, WithDecryption=True ) return res['Parameter']['Value'] def reaction_slack(body: dict, token: str, name: str) -> None: channel = body['event']['channel'] timestamp = body['event']['event_ts'] headers = { 'Content-Type': 'application/json', 'Authorization': f'Bearer {token}' } url = 'https://slack.com/api/reactions.add' data = { 'channel': channel, 'name': name, 'timestamp': timestamp } try: response = requests.post(url, headers=headers, data=json.dumps(data)) except requests.exceptions.RequestException as e: logger.error(e) else: logger.info(response.status_code) logger.info(response.text)
デプロイ
--parameter-overrides
を追加して、SSMパラメータストアのKey名を渡しています。
sam build --use-container sam package \ --output-template-file packaged.yaml \ --s3-bucket cm-fujii.genki-deploy sam deploy \ --template-file packaged.yaml \ --stack-name Slack-Team-IoT-Reaction-Bot-Stack \ --s3-bucket cm-fujii.genki-deploy \ --capabilities CAPABILITY_NAMED_IAM \ --parameter-overrides SlackAppTokenKey=/Slack/Toekn/Team-IoT-Reaction-Bot \ --no-fail-on-empty-changeset
動作確認
Slackで「いいね」を含むメッセージを投稿すると、:good:
リアクションが付与されました!!
さいごに
この内容を応用すれば、NGワードゲーム(日替わり)が作れそうですね。