SlackのInteractiveComponent
Making messages interactive | Slack
SlackBotでもInteractiveComponent自体を投稿することはできるが(単にattachmentsを付与すれば良い)、アクションのリダイレクト先を指定することができないのでSlackAppを作成する必要がある
※: SlackBotとSlackAppは別物
想定仕様
- EventAPIとInteractiveComponentのリダイレクト先に指定するサーバはAPI Gateway + Lambdaを使う
 - Flaskライクに書いたAPIをAPI Gateway + Lambda etc..へデプロイできるフレームワークChaliceを使う
 - SlackAppにメンションを飛ばすとInteractiveComponentを返す
- EventSubscribe(EventAPI) で 
app_mentionイベントをlistenする - RedirectURLは 
foo-bar-domain/apiを想定 
 - EventSubscribe(EventAPI) で 
 - InteractiveComponentにはYes/Noボタンを付ける
- どちらかのボタンが押されたとき対応する文章を返す
 - RedirectURLは 
foo-bar-domain/api/reactionとする 
 
SlackAppの作成
Slack API | Slack でStartBuildingボタンを押すとアプリを作成することができます
つづけて、 - BotUserの作成 - InteractiveComponentsの有効化 - Permissionの設定 - chat:write:bot - bot BotUser作成したときに付与される
をしておきます。最後にInstallAppすることでWorkspaceへAppをインストールします
RedirectURLはデプロイ後、URLが決定したら設定します
ChaliceでEventAPI、InteractiveComponentのRedirectを受けるAPIを作る
chaliceをインストールしてプロジェクトを作成します
AWS Chalice — Python Serverless Microframework for AWS 1.8.0 documentation
$ pip install chalice
$ chalice new-project foo_bar_api
AWSのCredentialsが正しくlocalで設定されてる場合この時点でデプロイ可能です
IAM、APIGateway、Lambdaが作成され、APIGatewayのURLが表示されます
$ chalice deploy
削除も簡単です
紐づくIAMまで削除してくれます
$ chalice delete
localserverで開発も可能です
hotreload付き
$ chalice local
ひとまずlocalで開発したい、ログが見たい場合は chalice local + ngrok が良さそうな雰囲気です
ngrok - secure introspectable tunnels to localhost
app.pyを編集します
import json
import logging
import urllib
from chalice import Chalice
app = Chalice(app_name='foo-bar')
# ログレベル: =INFOの情報も出力する
app.debug = True
logger = logging.getLogger()
logger.setLevel(logging.INFO)
# SlackAppのSettings > Basic InformationのApp Credentials参照
BOT_OAUTH_TOKEN = "xoxb-foo-bar-your-credentials"
# http requestのtokenフィールドの値
VERIFICATION_TOKEN = "foo-bar-your-verificationtoken"
# InteractiveComponentの実態 attachmentsに載せる情報
attachments_json = [
    {
        "fallback": "Upgrade your Slack client to use messages like these.",
        "color": "#258ab5",
        "attachment_type": "default",
        "callback_id": "hogehoge",
        "actions": [
            {
                "name": "yes",
                "text": "YES",
                "value": "y",
                "type": "button"
            },
            {
                "name": "yes",
                "text": "NO",
                "value": "n",
                "type": "button"
            }
        ]
    }
]
@app.route('/', methods=['POST'])
def handler():
    request = app.current_request
    # EventAPIのRedirectURLの認証のためchallengeフィールドの値をそのまま返す
    if "challenge" in request.json_body:
        return request.json_body["challenge"]
    slack_event = request.json_body['event']
    # ループしないための対策 e.g. appがappへのmentionを含むケース
    if "bot_id" in slack_event:
        logger.warning("Ignore integration bot message")
    else:
        response = 'which is your choice'
        respond(response, slack_event, attachments_json)
    return "200 OK"
# InteractiveComponentのデータはx-www-form-urlencodedで渡される
@app.route('/reaction', methods=['POST'], content_types=["application/x-www-form-urlencoded"])
def reaction_handler():
    request = app.current_request
    body = urllib.parse.unquote_to_bytes(request.raw_body).decode()
    payload = body.split('=', 1)[1]
    payload_dict = json.loads(payload)
    # 選択された値を取得
    choose_value = payload_dict["actions"][0]["value"]
    if choose_value == 'y':
        response = "your choice is yes "
    else:
        response = "your choice is no"
    # InteractiveComponentはreturnしたstrで置き換えられる
    # 連打防止の為 InteractiveComponentは残らない設計がbetterとされている
    return response
def respond(response, slack_event, attachments=None):
    channel_id = slack_event["channel"]
    data = urllib.parse.urlencode(
        (
            ("token", BOT_OAUTH_TOKEN),
            ("channel", channel_id),
            ("text", response),
            ("attachments", attachments)
        )
    )
    data = data.encode("ascii")
    # chat.postMessageへPOSTする
    request = urllib.request.Request(
        "https://slack.com/api/chat.postMessage",
        data=data,
        method="POST"
    )
    request.add_header(
        "Content-Type",
        "application/x-www-form-urlencoded"
    )
    urllib.request.urlopen(request).read()
デプロイします
$ chalice deploy
- /api
 - /api/reaction
 
のURLが表示されると思います
それぞれ、EventSubscribe, InteractiveComponent のRedirectURLに設定します
試してみる
Slackでアプリのbotユーザに対し、メンションを飛ばすことで機能を確認できるかと思います
1つのアプリで複数のInteractiveComponentに対応する場合、メンションしたときのtextや、attachmentのcallback_idで分別することになると思います