Open2
Slackのソケットモードを試す
全然知らなかったのでちょっとお試し。
ソケットモードでは、Slack からのペイロードを受け付ける Web エンドポイントを提供するサーバーを構築する代わりに Slack と WebSocket のコネクション経由でやりとりすることができるようになります。
普通にやるなら、アプリ側のAPIサーバみたいなものを用意して公開してステートレスな感じで使うことになるのだろうけど、ソケットモードではアプリ側からSlackにWebSocketで繋げっぱなしにして、Slackがユーザ・アプリとのやりとりを中継するという感じみたい。
以下がまとまっていてわかりやすい。とりあえずSlack側の設定を一通り済ませる。
Slackアプリの作成。Pythonで。
uv init -p 3.12 slack-socket-mode-sample && cd $_
uv add slack_bolt
出力
+ slack-bolt==1.25.0
+ slack-sdk==3.36.0
サンプルコード。ショートカットのところだけ少しだけいじった(ショートカットちゃんとやったことがなかった・・・チャネルに紐づけるなら、メッセージショートカットのほうがよさそうに思う)
main.py
import logging
import os
from slack_sdk import WebClient
from slack_bolt import App
from slack_bolt.adapter.socket_mode import SocketModeHandler
logging.basicConfig(level=logging.DEBUG)
app = App(token=os.environ["SLACK_BOT_TOKEN"])
# イベント API
@app.message("こんにちは")
def handle_messge_evnts(message, say):
say(f"こんにちは <@{message['user']}> さん!")
@app.shortcut("socket-mode-shortcut")
def handle_shortcut(ack, body: dict, client: WebClient):
ack()
client.views_open(
trigger_id=body["trigger_id"],
view={
"type": "modal",
"callback_id": "modal-id",
"title": {"type": "plain_text", "text": "タスク登録"},
"submit": {"type": "plain_text", "text": "送信"},
"close": {"type": "plain_text", "text": "キャンセル"},
"blocks": [
{
"type": "input",
"block_id": "input-task",
"element": {
"type": "plain_text_input",
"action_id": "task_input",
"multiline": True,
"placeholder": {"type": "plain_text", "text": "タスク詳細"},
},
"label": {"type": "plain_text", "text": "タスク"},
},
{
"type": "input",
"block_id": "select-channel",
"element": {
"type": "conversations_select", # チャネル/DMから選択可能
"action_id": "channel",
"default_to_current_conversation": False,
"filter": {"include": ["public", "private"]}, # 必要に応じて絞る
},
"label": {"type": "plain_text", "text": "投稿先チャネル"},
},
],
},
)
@app.view("modal-id")
def handle_view_submission(ack, body, view, client: WebClient, logger):
values = view["state"]["values"]
task_text = values["input-task"]["task_input"].get("value", "").strip()
selected = values["select-channel"]["channel"] # conversations_select の選択結果
channel_id = selected.get("selected_conversation")
if not task_text:
return ack(response_action="errors",
errors={"input-task": "入力が空です。内容を入力してください。"})
if not channel_id:
return ack(response_action="errors",
errors={"select-channel": "投稿先チャネルを選択してください。"})
ack()
try:
client.chat_postMessage(
channel=channel_id,
text=f":memo: 新規タスク(<@{body['user']['id']}> より):\n```\n{task_text}\n```"
)
logger.info(f"Posted to channel {channel_id}: {task_text}")
except Exception as e:
logger.exception(f"Failed to post message: {e}")
if __name__ == "__main__":
handler = SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"])
handler.start()
起動
export SLACK_APP_TOKEN=xapp-XXXXXXXX # ソケットモード有効時に生成されたトークン
export SLACK_BOT_TOKEN=xoxb-XXXXXXXX # アプリをワークスペースインストール時に生成されたトークン
uv run main.py
実際に試してみた。