
Slack Bolt for Python における ack の使い方徹底解説
Slack アプリ開発では、ユーザーからの各種インタラクション(スラッシュコマンド、ボタン、モーダル送信、ショートカットなど)に対し、必ず「リクエストを受領した」という応答を Slack に返す必要があります。これを行うのが ack 関数です。Slack は 3 秒以内に HTTP 200 の応答を返さないとタイムアウトエラーとなるため、すぐに ack を呼ぶことが必須となります。
本記事では、Slack Bolt for Python を用いて、ack の基本的な役割と主要なユースケース(スラッシュコマンド、ボタンアクション、モーダル送信、選択肢の動的提供)について、具体例とともに詳しく解説します。
1. ack の基本的な役割
ack は、Slack からのリクエストに対して「受信しました」と即座に返すための関数です。
応答が遅れるとタイムアウトエラーとなり、ユーザー側にエラー表示される。
インタラクティブな操作の場合、UI 上のローディング状態が解除される。
通常、リスナー関数の冒頭で必ず ack() を呼び出し、その後に重い処理や追加の API 呼び出しを実行します。これにより Slack には即座に 200 OK を返すことができ、ユーザー体験が向上します。
2. スラッシュコマンドでの ack の使い方
スラッシュコマンドは、ユーザーがコマンドを入力した際に発生するイベントです。
以下の例は、/hello コマンドを受け取り、発行者に挨拶メッセージを返信するサンプルコードです。
from slack_bolt import App
app = App(token="xoxb-***", signing_secret="***")
@app.command("/hello")
def handle_hello_command(ack, command, respond):
# まず即座に ack() を呼んで応答を返す
ack()
# ユーザーへ返信(デフォルトは発行者のみのエフェメラルメッセージ)
respond(f"こんにちは、<@{command['user_id']}> さん!")
if __name__ == "__main__":
app.start(port=3000)
ポイント:
必ず最初に ack() を呼ぶ。
応答メッセージは respond() を使って送信可能。
respond() に response_type: "in_channel" を指定すると、チャンネル全体に公開することもできる。
3. ボタンなどのブロックアクションでの ack の使い方
メッセージ内のボタンがクリックされた場合、app.action で登録したリスナーが呼ばれます。
以下は、ボタン(action_id が "delete_btn")がクリックされたときに、対象メッセージを削除する例です。
@app.action("delete_btn")
def handle_delete_button(ack, body, client):
# まず ack() を呼び、ボタンのローディング状態を解除
ack()
channel_id = body["channel"]["id"]
message_ts = body["message"]["ts"]
# メッセージを削除する API 呼び出し
client.chat_delete(channel=channel_id, ts=message_ts)
ポイント:
ボタン操作の場合も、最初に必ず ack() を呼ぶ。
その後、必要な Web API 呼び出し(例:chat_delete、chat_update 等)で UI の更新を行う。
4. モーダル送信(View Submission)での ack の使い方
モーダル内のフォーム送信時には、送信内容の検証を行い、条件に応じてエラーを返すかモーダルを閉じるかを選択します。
以下は、モーダル送信時にフィードバック入力の検証を行い、入力が不足している場合にエラーを返す例です。
@app.view("feedback_modal")
def handle_feedback_modal(ack, body, view, client):
state_values = view["state"]["values"]
feedback = state_values["feedback_block"]["feedback_input"]["value"]
# 入力値が空の場合、エラー情報を返す
if not feedback:
ack(response_action="errors", errors={
"feedback_block": "フィードバックを入力してください。"
})
return
# 正常な場合は単に ack() を呼んでモーダルを閉じる
ack()
# その後、たとえばユーザーへサンクスメッセージを送信する
user_id = body["user"]["id"]
client.chat_postMessage(
channel=user_id,
text="フィードバックありがとうございます!"
)
ポイント:
入力エラーがある場合は、ack() に response_action="errors" と errors オブジェクトを渡す。
条件を満たす場合は、単に ack() を呼ぶだけでモーダルは自動的に閉じる。
エラー応答を返した後は、以降の処理を中断(return)すること。
5. 選択肢(Options)の動的提供での ack の使い方
外部セレクトメニューなど、動的に選択肢を返す場合は、app.options を利用します。
以下は、選択肢として「寿司」と「ラーメン」を返す例です。
@app.options("food_menu")
def show_food_options(ack):
options = [
{
"text": {"type": "plain_text", "text": "寿司"},
"value": "sushi"
},
{
"text": {"type": "plain_text", "text": "ラーメン"},
"value": "ramen"
}
]
# 選択肢情報を ack() で返す
ack(options=options)
ポイント:
ack() の引数に options キーを指定し、Slack が期待するフォーマットの選択肢リストを返す。
外部セレクトなど、動的に選択肢を生成する場合に便利。
6. その他の注意点
3秒ルールの厳守:
すべてのインタラクションでは、3秒以内に ack() を返さないとタイムアウトエラーとなるため、重い処理は ack() 後に非同期で実行するなどの工夫が必要です。エラー処理:
リスナー内で予期しないエラーが発生した場合も、可能な限り ack() を返すようにして、Slack 側でタイムアウトとならないように注意しましょう。1リクエストにつき1回の ack():
同一リクエストに対して複数回 ack() を呼ばないように、各分岐後は必ず return するなどの対策を取りましょう。Lazy Listener の活用:
AWS Lambda などサーバーレス環境で動作させる場合、先に ack() を返し、後続処理を別途非同期に実行する Lazy Listener の仕組みも検討するとよいでしょう。
まとめ
Slack Bolt for Python では、各種インタラクションに対して 必ず ack() を呼び、Slack に即座に 200 OK を返す ことが求められます。
スラッシュコマンド: コマンド受信時に即座に ack() を呼び、続けて respond() などで応答メッセージを送信。
ブロックアクション(ボタンなど): ユーザー操作に対して、まず ack() を実行し、必要に応じた Web API 呼び出しで UI を更新。
モーダル送信: 入力検証後、エラーがあれば ack(response_action="errors") でエラー情報を返し、正常な場合は単に ack() でモーダルを閉じる。
選択肢提供: 外部セレクト等で、ack(options=...) を利用して動的な選択肢リストを返す。
これらのパターンを正しく実装することで、ユーザーにとってスムーズかつ応答の速い Slack アプリを構築することができます。実装時は「まず ack() を呼ぶ」という基本を忘れずに、各種ユースケースに応じた応答内容を適切に返すようにしましょう。
以上が、Slack Bolt for Python における ack の使い方の解説とサンプルコードになります。この記事を参考に、ぜひ実際のアプリ開発にお役立てください。