Open2

Slackのソケットモードを試す

kun432kun432

全然知らなかったのでちょっとお試し。

https://docs.slack.dev/tools/java-slack-sdk/ja-jp/guides/socket-mode/

https://docs.slack.dev/tools/bolt-python/ja-jp/concepts/socket-mode/

ソケットモードでは、Slack からのペイロードを受け付ける Web エンドポイントを提供するサーバーを構築する代わりに Slack と WebSocket のコネクション経由でやりとりすることができるようになります。

普通にやるなら、アプリ側のAPIサーバみたいなものを用意して公開してステートレスな感じで使うことになるのだろうけど、ソケットモードではアプリ側からSlackにWebSocketで繋げっぱなしにして、Slackがユーザ・アプリとのやりとりを中継するという感じみたい。

kun432kun432

以下がまとまっていてわかりやすい。とりあえずSlack側の設定を一通り済ませる。

https://qiita.com/seratch/items/1a460c08c3e245b56441

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

実際に試してみた。