Help us understand the problem. What is going on with this article?

Slack で自動返信するサーバレスBOTを作りました

More than 3 years have passed since last update.

💡作ったもの

Slack の特定の Reaction に反応し、Reactionで反応し返してくれるサーバレスBOTです。

 Slack に登録されたものから適当にキーワードを拾って、定型文を返信するだけのBOTです。APIを叩き叩かれるだけなので Lambda でやります。slackbot ライブラリや Hubot みたいな軟弱なものは使いません。

この処理を使いまわして、Slack BOT でなんかやる系のネタ記事をいくか作る予定です。

:octocat: (追記) 成果物

Serverless Framework 設定と共に公開しました。構築手順はこちらのほうが簡単です。
saitota/SlackServerlessReplyBot: It is a serverless BOT which receives Slack's Public Channel message and reply if there includes a specific keyword.

✋手順

必要なもの

  • Slack アカウント
  • AWS アカウント

Slack/BOT作成

BOTから作っていきます。

  • アプリケーション作成

  • Bot User

    • Display Name:適当に
    • Default Username:適当に

トークン発行とアクセス許可をします

  • Permissions
    • OAuth & Permissions
      • OAuth Access Token:'xoxp-000000000000-000000000000-000000000000-0x0x0x0x0x0x0x0x0x0x0x0x0x0x0x0x'
      • Bot User OAuth Access Token:'xoxb-000000000000-0x0x0x0x0x0x0x'
    • OAuth & Permissions

Lambda/API Gateway

前提として、チャレンジパラメータといういい感じの認証コードを返却するような Lambda Functrionを作成する必要があります。
参考:url_verification event | Slack

url_verification
{
    "token": "Jhj5dZrVaK7ZwHHjRyZWjbDl",
    "challenge": "3eZbrw1aBm2rZgRNFdxV2595E9CY3gmdALWMmHkvFXO7tYXAYM8P",
    "type": "url_verification"
}
  • API-Gateway
  • Lambda
    • 名前:CatchKeyword
    • ランタイム:Python3.6
    • ロール:lambda_basic_execution
    • 環境変数
      • OAUTH_TOKEN: xoxp-000000000000-000000000000-000000000000-0x0x0x0x0x0x0x0x0x0x0x0x0x0x0x0x
      • BOT_TOKEN: xoxb-000000000000-0x0x0x0x0x0x0x0x0x0x0x0x
      • HOOK_KEYWORD: poop
      • REPLY_WORD: :poop: :poop: :poop:
      • BOT_NAME: poopman
    • トリガ:API-Gateway

Lambdaの標準ライブラリだけでいけるようにしました。
参考:chat.postMessage method | Slack

main.py
import json
import logging
import urllib.request
import os

print('Loading function... ')
logger = logging.getLogger()
logger.setLevel(logging.INFO)

def handler(event, context):
    #getenv
    OAUTH_TOKEN = os.environ['OAUTH_TOKEN']
    BOT_TOKEN = os.environ['BOT_TOKEN']
    HOOK_KEYWORD = os.environ['HOOK_KEYWORD']
    REPLY_WORD = os.environ['REPLY_WORD']
    BOT_NAME = os.environ['BOT_NAME']

    #受信したjsonをLogsに出力
    logging.info(json.dumps(event))

    print (type(event))
    # json処理
    if 'body' in event:
        body = json.loads(event.get('body'))
    elif 'token' in event:
        body = event
    else:
        logger.error('unexpected event format')
        return {'statusCode': 500, 'body': 'error:unexpected event format'}

    #url_verificationイベントに返す
    if 'challenge' in body:
        challenge = body.get('challenge')
        logging.info('return challenge key %s:', challenge)
        return {
            'isBase64Encoded': 'true',
            'statusCode': 200,
            'headers': {},
            'body': challenge
        }
    #SlackMessageに特定のキーワードが入っていたときの処理(poopに反応して処理します)
    if body.get('event').get('text') == HOOK_KEYWORD:
        logger.info('hit!: %s', HOOK_KEYWORD)
        url = 'https://slack.com/api/chat.postMessage'
        headers = {
            'Content-Type': 'application/json; charset=UTF-8',
            'Authorization': 'Bearer {0}'.format(BOT_TOKEN)
        }
        data = {
            'token': OAUTH_TOKEN,
            'channel': body.get('event').get('channel'),
            'text': REPLY_WORD,
            'username': BOT_NAME
        }
        # POST処理 
        req = urllib.request.Request(url, data=json.dumps(data).encode('utf-8'), method='POST', headers=headers)
        res = urllib.request.urlopen(req)
        logger.info('post result: %s', res.msg)
        return {'statusCode': 200, 'body': 'ok'}
    return {'statusCode': 200, 'body': 'quit'}

Slack/BOTの設定を追加

Event Subscriptions で呼び出すトリガを設定します。すべてのPublicチャネルの Message を受信します。

 以上で動くことを確認できました。

ハマった所

 何度もSlackメッセージが届く事象が起き(数分間隔を置いて、合計4回届く)、Lambda側で処理が悪くてリトライされているのかと思ったけどログを見ると何度も API リクエストが届いているという事象がありました。2度め以降のリクエストを見たところ headers"X-Slack-Retry-Num": "1", "X-Slack-Retry-Reason": "http_error" となっており、HTTP レスポンスで "statusCode": 2xx を返さないとリトライされてしまう
Slack 側のリトライ仕様でした。API-Gateway できちんと 200 を返すよう作り直し、解消しました。
 Lambda は return {'statusCode': 200, 'body': 'hogehoge'} で返さないと、502 Bad Gateway を返してしまうので、きちんと HTTP リターンコードを設定して return する必要がありました。

📝あとがき

 Slack の Incoming Webhook で同じ実装をする場合、API-Gateway と連携させようとするとマッピングテンプレート作ったりで非常に面倒だったのですが、今回 Event Subscribe をつかったら楽ちんでした。なお、今回はすべてのパブリックチャンネルのメッセージを拾っているので、Lambda 料金がアレかもしれません。今回特に書いていませんが、Cloud9 がテストやデプロイでなかなか便利だったので使いこなしていきたいです。
 公開用に Serverless Framework も使ってみましたが簡単にAPI Gatewayやアプリケーションがデプロイで来て定義も正しくできるので、とてもいい感じでした。

📘参考にしました

AWS Lambda + API Gateway で Slack Bot を動かす - Speee DEVELOPER BLOG
オウム返し slack bot をぱっとつくる - Qiita
Slack Event APIを使ってサーバレスなSlackボットを作る - Qiita

saitotak
羊なので毎日ふわふわしています
infra-workshop
インフラ技術を勉強したい人たちのためのオンライン勉強会です
https://wp.infra-workshop.tech/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away