はじめに
本記事ではSlackアプリでの動的なメッセージ更新のtipsを紹介します。基本的なSlackアプリの作り方は割愛します。巷に良記事が溢れているのでそちらをご覧ください。
対象読者としてはある程度Slackアプリ開発をかじったことがある人を想定しています。
使用ツール
- Bolt for Python
- Block Kit Builder
- AWS lambda
上の二つはもうSlackアプリ開発においては外すことができませんね。
AWS lambdaでのSlackアプリ作成に関しては、Slack の bot を自作する(AWS Lambda + API Gateway)の記事が大変参考になります。
Slackにおけるmessage構造
Slackはプレーンなメッセージの他にもボタンや画像、セクションメニューやカレンダーなど、さまざまなコンポーネントを表示することができます。これらは内部的にはjson形式のblock
という単位で定義されており、それらを組み合わせることでとてもリッチなメッセージを作ることができます。
Block Kit Builderで実際のblockを見てみる
実際にBlock Kit Builderを使ってメッセージがどんなblock
構造になっているか見てみます。
左側が実際に表示されるメッセージ、右側がそのblock構造になります。
二枚目の画像ではplain textとinputのblockが順にリストになっているのがわかります。
それぞれのコンポーネントを定義するblockを、表示したい順番にリストにしていけばいいのでとてもシンプルな構造です。
Block idという存在
各blockにはblock_id
という要素を持たせることができます。
これはblockを一意に識別するためのもので、任意の文字列(最大255文字)をセットすることができます。
何かユーザがインタラクティブなアクションをした時に、このidによってどのblockソースを使ったのかを識別することができるようになります。
たとえば、複数の入力フォームが用意されている以下のようなメッセージをご覧ください。一つ目の入力フォームのblock_idにはform1
、二つ目にはform2
としました。処理を受け取る側ではこのidを参照することによって、どちらのフォームによる入力なのかを識別することができます。
任意の文字列を指定できることを活用する
さて、ここが本題です。先述の通りblock_idには任意の文字列を指定することができます。このidをそのblockが保持しているステータスと捉えると、たとえばこんな機能が簡単にできます。
はじめblock_id = '1'
という状態で保持しておき、それをボタンアクションごとに参照して1を足したものをtextとして表示しています。それを再度block_idに渡すことで値の更新が再起的にできるようになります。
chat_update
と組み合わせることで、新たなメッセージを表示することなく動的に更新することができるようになりました。
以下はこのデモに使ったlambda_functionのコードになります。アクションごとにblock_idを取り出す、処理を加えた後にまたblock_idに戻す、という流れを確認してください。
import os
from slack_bolt import App
from slack_bolt.adapter.aws_lambda import SlackRequestHandler
app = App(
process_before_response=True,
token=os.environ.get("SLACK_BOT_TOKEN"),
signing_secret=os.environ.get("SLACK_SIGNING_SECRET"),
)
def lambda_handler(event, context):
slack_handler = SlackRequestHandler(app=app)
return slack_handler.handle(event, context)
@app.event("app_mention")
def say_hello(say, body, client):
text = "1"
say(blocks=generate_block(text))
@app.action("button-action")
def button(say, body, client):
channel = body['container']['channel_id']
ts = body['message']['ts']
block_id = int(body['message']['blocks'][0]['block_id']) + 1
client.chat_update(
channel=channel,
ts=ts,
blocks=generate_block(str(block_id)),
text = "alt_message"
)
def generate_block(text):
return [
{
"type": "section",
"block_id": text,
"text": {
"type": "mrkdwn",
"text": text
},
"accessory": {
"type": "button",
"text": {
"type": "plain_text",
"text": "Click",
"emoji": True
},
"value": "click_me_123",
"action_id": "button-action"
}
}
]
終わりに
もちろんボタンアクションに限らず全てのアクションで活用することができます。
blockにユーザのアクションの文脈を持たせることで、異なるコンポーネントの組み合わせ処理を実現できます。
inputアクションなども織り交ぜると結構複雑なことも実現できるのでぜひお試しください。