[Slack App] 動的にメッセージを更新するテクニック

![画面収録_2022-06-06_19_20_28_AdobeCreativeCloudExpress.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/503266/a32f4d5b-1c6f-7576-eee4-105dc3973dac.gif)

# はじめに
本記事ではSlackアプリでの動的なメッセージ更新のtipsを紹介します。基本的なSlackアプリの作り方は割愛します。巷に良記事が溢れているのでそちらをご覧ください。
対象読者としてはある程度Slackアプリ開発をかじったことがある人を想定しています。

# 使用ツール
- [Bolt for Python](https://slack.dev/bolt-python/ja-jp/tutorial/getting-started)
- [Block Kit Builder](https://app.slack.com/block-kit-builder/TH5FKCE6T#%7B%22blocks%22:%5B%7B%22type%22:%22divider%22%7D%5D%7D)
- AWS lambda

上の二つはもうSlackアプリ開発においては外すことができませんね。
AWS lambdaでのSlackアプリ作成に関しては、[Slack の bot を自作する（AWS Lambda + API Gateway）](https://qiita.com/landwarrior/items/dfac36a1aadac9ab0cd5)の記事が大変参考になります。

# Slackにおけるmessage構造
Slackはプレーンなメッセージの他にもボタンや画像、セクションメニューやカレンダーなど、さまざまなコンポーネントを表示することができます。これらは内部的にはjson形式の`block`という単位で定義されており、それらを組み合わせることでとてもリッチなメッセージを作ることができます。

## Block Kit Builderで実際のblockを見てみる
実際にBlock Kit Builderを使ってメッセージがどんな`block`構造になっているか見てみます。
左側が実際に表示されるメッセージ、右側がそのblock構造になります。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/503266/9d762099-ee81-c424-db98-bdb890b97185.png" width=1200>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/503266/942b0ad6-5bdb-abc3-a28b-4463a014ef36.png" width=1200>
二枚目の画像ではplain textとinputのblockが順にリストになっているのがわかります。
それぞれのコンポーネントを定義するblockを、表示したい順番にリストにしていけばいいのでとてもシンプルな構造です。

## Block idという存在
各blockには`block_id`という要素を持たせることができます。
これはblockを一意に識別するためのもので、任意の文字列（最大255文字）をセットすることができます。
何かユーザがインタラクティブなアクションをした時に、このidによってどのblockソースを使ったのかを識別することができるようになります。
たとえば、複数の入力フォームが用意されている以下のようなメッセージをご覧ください。一つ目の入力フォームのblock_idには`form1`、二つ目には`form2`としました。処理を受け取る側ではこのidを参照することによって、どちらのフォームによる入力なのかを識別することができます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/503266/7da1975a-4631-75b0-48cf-ccdcc37a092d.png" width=1200>

## 任意の文字列を指定できることを活用する
さて、ここが本題です。先述の通りblock_idには任意の文字列を指定することができます。このidをそのblockが保持しているステータスと捉えると、たとえばこんな機能が簡単にできます。

![画面収録_2022-06-06_19_20_28_AdobeCreativeCloudExpress.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/503266/7ac748b8-2b21-e951-aad0-5e746a4e3ab1.gif)

はじめ`block_id = '1'`という状態で保持しておき、それをボタンアクションごとに参照して1を足したものをtextとして表示しています。それを再度block_idに渡すことで値の更新が再起的にできるようになります。
`chat_update`と組み合わせることで、新たなメッセージを表示することなく動的に更新することができるようになりました。

以下はこのデモに使ったlambda_functionのコードになります。アクションごとにblock_idを取り出す、処理を加えた後にまたblock_idに戻す、という流れを確認してください。

```python:lambda_function.py
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アクションなども織り交ぜると結構複雑なことも実現できるのでぜひお試しください。