Edited at

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

More than 1 year has passed since last update.


💡作ったもの

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

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

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


(追記) 成果物

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