Help us understand the problem. What is going on with this article?

Slack ソケットモードの最も簡単な始め方

ソケットモード(Socket Mode)とは?

Slack は、昨年の年次イベント Frontiers にて「ソケットモード」という Slack アプリの新しい通信方式を発表しました

これまでの Slack アプリ開発では、イベント API やモーダル送信、ボタンクリック、スラッシュコマンドなどのインタラクティブな操作のハンドリングには、公開された Web エンドポイントを用意して Slack からのリクエストを受信する必要がありました。

ソケットモードは、これを WebSocket での接続に切り替えることができる機能です。昔から Slack アプリやボットに慣れ親しんでいる方であれば Hubot や RTM (Real Time Messaging) API のように接続できる方式という言い方がわかりやすいかもしれません。

ソケットモード GA リリース :tada:

ソケットモードは昨年のベータ期間を経て 2021年1月12日(米国時間)に正式リリースとなりました! :tada: 英語となりますが、Slack Platform Blog に紹介記事が出ていますので、ぜひ読んでみてください。

この記事では、このソケットモードを試す方法を日本語で紹介したいと思います。

ソケットモードを試す

Slack アプリを設定する

まず https://api.slack.com/apps?new_app=1 にアクセスして新しい Slack アプリをつくります。

ソケットモードを有効にする

アプリを作成すると、以下のようにソケットモード(Socket Mode)設定への導線がありますので Settings > Socket Mode へ遷移します。

Enable Socket Mode をトグルして有効にします。

以下のようなモーダルが開きますので、トークンにソケットモード関連であることがわかるような名前をつけて Generate ボタンを押します。元々設定されている connections:write というスコープは、WebSocket 接続 URL の取得に必要となりますので、削除しないようにしてください。

Generate を押すと、モーダルが切り替わって以下の画面のように xapp- で始まるトークンが表示されます。このトークンを後ほど SLACK_APP_TOKEN として使いますので、控えておいてください。

これで、ソケットモード自体は有効になりました。それでは、インタラクティブな機能を有効にしていきましょう。

グローバルショートカットを設定する

まずはショートカットを追加してみましょう。Features > Interactivity & Shortcuts に遷移してください。この画面にくるとすでに InteractivityOn になっています。そして Request URL を設定する必要がない旨、表示されています。以下の Create New Shortcut ボタンからショートカットを設定しましょう。

今回の例ではグローバルショートカットを使いますので、そのまま Next をクリックしてください。

NameShort Description は、Slack ワークスペース内でエンドユーザーの目に触れますので、わかりやすい日本語のものを設定するとよいでしょう。Callback ID はアプリ側でハンドリングする時に使いますので、アルファベットのみで socket-mode-shortcut として Create ボタンを押してください。

元の画面に戻るとショートカットが追加されているはずです。これで完了です。画面下部の Save Changes ボタンを押してください

イベント API を設定する

次にイベント API を設定します。こちらはデフォルトでは有効になっていませんので On にトグルします。

サブスクライブするイベントはいろいろあるので、お好きなものを試してもらえればと思いますが、以下のコード例をそのまま動かすなら message.channels イベントを指定してください。こちらも Save Changes ボタンを押すのも忘れずに。

その他の設定

インストールする前にその他の設定をしておきます。

まず、チャンネルにメッセージを投稿するのはよくあるユースケースだと思いますので Features > OAuth & Permissions に移動して chat:write スコープを追加しておきましょう。

ボットユーザーの設定を行います。 Features > App Home に移動して App Display Name のところの Edit ボタンから変更します。

一つ目の項目がユーザーの目に触れます。二つ目はアルファベットでユニークな名前として指定しておいてください。

ワークスペースにインストールする

以上で設定完了です。ワークスペースにインストールしましょう。 Settings > Install App へ移動して Install to Workspace ボタンをクリックしてください。

このようにいつもの確認画面が表示されます。

インストールが完了すると xoxb- ではじまる SLACK_BOT_TOKEN が発行されていますので、この値を後ほどアプリで使用します。

これで Slack アプリの設定、ワークスペースでの有効化が完了しました。あとは Bolt アプリを実装して起動するだけです!

ちなみにアプリが動作すると以下のような感じになります(左はショートカットから起動したモーダル、右はイベント API を使ったキーワード反応)。

各言語でのサンプル例

JavaScript で始める

12 以上のバージョンの Node.js があればすぐに試せます。こちらの日本語ドキュメントも合わせて参考にしてください。

npm init -y
npm i @slack/bolt@3

index.js というファイル名で以下のソースコードを保存します。

const { App } = require('@slack/bolt');

const app = new App({
  logLevel: 'debug',  // これはログレベルの調整なので削除しても OK です
  socketMode: true,
  token: process.env.SLACK_BOT_TOKEN,
  appToken: process.env.SLACK_APP_TOKEN
});

// グローバルショートカット
app.shortcut("socket-mode-shortcut", async ({ ack, body, client }) => {
  await ack();
  await 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": "input",
            "multiline": true,
            "placeholder": {
              "type": "plain_text",
              "text": "タスクの詳細・期限などを書いてください"
            }
          },
          "label": {
            "type": "plain_text",
            "text": "タスク"
          }
        }
      ]
    }
  });
});

app.view("modal-id", async ({ ack, view, logger }) => {
  logger.info(`Submitted data: ${view.state.values}`);
  await ack();
});

// イベント API
app.message("こんにちは", async ({ message, say }) => {
  await say(`:wave: こんにちは <@${message.user}>!`);
});

(async () => {
  await app.start();
  console.log('⚡️ Bolt app started');
})();

npx node index.js で起動してみてください。特にエラーが出なければ OK です。

アプリの準備ができましたので、インストールした Slack ワークスペースで、ボットユーザーをテスト用のチャンネルに招待して message イベントの受信やグローバルショートカットの実行を試してみてください。

Python で始める

Python で始める場合は Python 3.6 以上の環境さえあれば、以下の手順で仮想環境をつくって、すぐにアプリを立ち上げることができます。

python3 -m venv .venv
source .venv/bin/activate
echo 'slack_bolt>=1.2' > requirements.txt
pip install -U pip
pip install -r requirements.txt

次に app.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": "input",
                        "multiline": True,
                        "placeholder": {
                            "type": "plain_text",
                            "text": "タスクの詳細・期限などを書いてください",
                        },
                    },
                    "label": {"type": "plain_text", "text": "タスク"},
                }
            ],
        },
    )

@app.view("modal-id")
def handle_view_submission(ack, view, logger):
    logger.info(f"Submitted data: {view['state']['values']}")
    ack()

if __name__ == "__main__":
    handler = SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"])
    handler.start()

あとはトークンを環境変数に設定して python app.py を実行するだけで起動できます。

export SLACK_APP_TOKEN=xapp-<自分のトークンの値>
export SLACK_BOT_TOKEN=xoxb-<自分のトークンの値>
python app.py

デバッグログが出力されたかと思いますが、やっていることを解説すると以下のようになります。

# SLACK_BOT_TOKEN のチェック
Sending a request - url: https://www.slack.com/api/auth.test, query_params: {}, body_params: {}, files: {}, json_body: {}, headers: { ... }
Received the following response - status: 200, headers: { ... }, body: {'ok': True, 'url': 'https://xxx.slack.com/', 'team': 'xxx', 'user': 'socket-san', 'team_id': 'T03E94MJU', 'user_id': 'U111', 'bot_id': 'B111', 'is_enterprise_install': False}

# SLACK_APP_TOKEN を使って WebSocket 接続 URL を取得
Sending a request - url: https://www.slack.com/api/apps.connections.open, query_params: {}, body_params: {}, files: {}, json_body: None, headers: { ... }
Received the following response - status: 200, headers: { ... }, body: {'ok': True, 'url': 'wss://wss-primary.slack.com/link/?ticket=xxx&app_id=yyy'}

# WebSocket 接続を確立
A new session has been established (session id: edf6378e-852e-4b36-8720-22e0b2952a76)
⚡️ Bolt app is running!

# 最初に hello メッセージを受信
Starting to receive messages from a new connection (session id: edf6378e-852e-4b36-8720-22e0b2952a76)
on_message invoked: (message: {"type":"hello","num_connections":1,"debug_info":{"host":"applink-canary-xxx-yyy","build_number":10,"approximate_connection_time":18060},"connection_info":{"app_id":"A111"}})
A new message enqueued (current queue size: 1)
A message dequeued (current queue size: 0)
Message processing started (type: hello, envelope_id: None)
Message processing completed (type: hello, envelope_id: None)

より詳細なログを出したい場合は trace_enabledall_message_trace_enabledTrue に指定してみてください。

if __name__ == "__main__":
    handler = SocketModeHandler(
        app=app,
        app_token=os.environ["SLACK_APP_TOKEN"],
        trace_enabled=True,
    )
    handler.start()

アプリの準備ができましたので、インストールした Slack ワークスペースで、ボットユーザーをテスト用のチャンネルに招待して message イベントの受信やグローバルショートカットの実行を試してみてください。

なお、 websocket_client, aiohttp, websockets というライブラリを標準でサポートしていますので、そちらを使う場合は、こちらのドキュメントを参考にしてください。

Java で始める

基本的にはこのドキュメントがすでに日本語になっていますので、そちらをみていただければ十分なのですが、上記の例と同じものはこのようになります。

JDK のバージョンは 8 以上で利用可能ですが 11 などより新しいバージョンを推奨します。

ここではビルドツールに Gradle を使いましょう(Maven の方がよければそれももちろん可能です)。build.gradle を以下の内容で保存してください。

plugins {
  id("application")
}
repositories {
  mavenCentral()
}
dependencies {
  implementation("com.slack.api:bolt-socket-mode:1.5.0")
  implementation("org.glassfish.tyrus.bundles:tyrus-standalone-client:1.17")
  implementation("org.slf4j:slf4j-simple:1.7.30")
}
application {
  mainClassName = "hello.MyApp"
}

ソースコードは以下の一つだけです。 src/main/java/hello/MyApp.java というファイル名で保存します。

package hello;

import com.slack.api.bolt.App;
import com.slack.api.bolt.socket_mode.SocketModeApp;
import com.slack.api.model.event.MessageEvent;
import com.slack.api.model.view.View;

import static com.slack.api.model.block.Blocks.asBlocks;
import static com.slack.api.model.block.Blocks.input;
import static com.slack.api.model.block.composition.BlockCompositions.plainText;
import static com.slack.api.model.block.element.BlockElements.plainTextInput;
import static com.slack.api.model.view.Views.*;

public class MyApp {
  public static void main(String[] args) throws Exception {
    System.setProperty("org.slf4j.simpleLogger.log.com.slack.api", "debug");

    // SLACK_BOT_TOKEN という環境変数が設定されている前提
    App app = new App();

    // イベント API
    app.event(MessageEvent.class, (req, ctx) -> {
      ctx.say(":wave: こんにちは <@" + req.getEvent().getUser() + ">!");
      return ctx.ack();
    });

    // ショートカットとモーダル
    app.globalShortcut("socket-mode-shortcut", (req, ctx) -> {
      View modalView = view(v -> v
        .type("modal")
        .callbackId("modal-id")
        .title(viewTitle(title -> title.type("plain_text").text("タスク登録").emoji(true)))
        .submit(viewSubmit(submit -> submit.type("plain_text").text("送信").emoji(true)))
        .close(viewClose(close -> close.type("plain_text").text("キャンセル").emoji(true)))
        .blocks(asBlocks(input(i -> i
          .blockId("input-task")
          .element(plainTextInput(pti -> pti.actionId("input").multiline(true)))
          .label(plainText(pt -> pt.text("タスクの詳細・期限などを書いてください")))
        )))
      );
      ctx.asyncClient().viewsOpen(r -> r
        .triggerId(req.getPayload().getTriggerId())
        .view(modalView)
      );
      return ctx.ack();
    });
    app.viewSubmission("modal-id", (req, ctx) -> {
      ctx.logger.info("Submitted data: {}", req.getPayload().getView().getState().getValues());
      return ctx.ack();
    });

    // SLACK_APP_TOKEN という環境変数が設定されている前提
    new SocketModeApp(app).start();
  }
}

以下の手順で起動してみてください。接続して hello メッセージを受信できるはずです。

export SLACK_APP_TOKEN=xapp-<自分のトークンの値>
export SLACK_BOT_TOKEN=xoxb-<自分のトークンの値>
gradle run

アプリの準備ができましたので、インストールした Slack ワークスペースで、ボットユーザーをテスト用のチャンネルに招待して message イベントの受信やグローバルショートカットの実行を試してみてください。

なお、Java-WebSocket というライブラリも WebSocket クライアントの実装としてサポートしていますので、そちらを使いたい場合は、切り替え手順をこちらで確認してください。

Kotlin で始める

上の Java とほぼ同様ですが、Kotlin のビルド設定です。

plugins {
  id("org.jetbrains.kotlin.jvm") version "1.4.21-2"
  id("application")
}
repositories {
  mavenCentral()
}
dependencies {
  implementation(platform("org.jetbrains.kotlin:kotlin-bom"))
  implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
  implementation("com.slack.api:bolt-socket-mode:1.5.0")
  implementation("com.slack.api:slack-api-model-kotlin-extension:1.5.0")
  implementation("org.glassfish.tyrus.bundles:tyrus-standalone-client:1.17")
  implementation("org.slf4j:slf4j-simple:1.7.30") // または logback-classic など
}
application {
  mainClassName = "MyAppKt" // ソースファイル名の末尾、拡張子の代わりに "Kt" をつけた命名になります
}

ソースコードは src/main/kotlin/MyApp.kt として以下の内容で保存します。

import com.slack.api.bolt.App
import com.slack.api.bolt.socket_mode.SocketModeApp
import com.slack.api.model.event.MessageEvent
import com.slack.api.model.kotlin_extension.view.blocks
import com.slack.api.model.view.Views.*

fun main() {
  System.setProperty("org.slf4j.simpleLogger.log.com.slack.api", "debug")

  // SLACK_BOT_TOKEN という環境変数が設定されている前提
  val app = App()

  // イベント API
  app.event(MessageEvent::class.java) { req, ctx ->
    ctx.say("こんにちは <@" + req.event.user + ">!")
    ctx.ack()
  }

  // ショートカットとモーダル
  app.globalShortcut("socket-mode-shortcut") { req, ctx ->
    val modalView = view { v -> v
      .type("modal")
      .callbackId("modal-id")
      .title(viewTitle { it.type("plain_text").text("タスク登録") })
      .submit(viewSubmit { it.type("plain_text").text("送信") })
      .close(viewClose { it.type("plain_text").text("キャンセル") })
      .blocks {
        input {
          blockId("input-task")
          element {
            plainTextInput {
              actionId("input")
              multiline(true)
            }
          }
          label("タスクの詳細・期限などを書いてください")
        }
      }
    }
    ctx.asyncClient().viewsOpen { it.triggerId(req.payload.triggerId).view(modalView) }
    ctx.ack()
  }
  app.viewSubmission("modal-id") { req, ctx ->
    ctx.logger.info("Submitted data: {}", req.payload.view.state.values)
    ctx.ack()
  }

  // SLACK_APP_TOKEN という環境変数が設定されている前提
  SocketModeApp(app).start()
}

こちらも同様に gradle run で接続できるはずです。

export SLACK_APP_TOKEN=xapp-<自分のトークンの値>
export SLACK_BOT_TOKEN=xoxb-<自分のトークンの値>
gradle run

アプリの準備ができましたので、インストールした Slack ワークスペースで、ボットユーザーをテスト用のチャンネルに招待して message イベントの受信やグローバルショートカットの実行を試してみてください。

次のステップ

ある程度動くようになったら、デプロイをどうするかを検討してみてください。

WebSocket で接続するアプリですので FaaS (Function as a Service) のようなソリューションはマッチしないでしょう。Docker イメージを作って ECS や Lightsail Containers などにデプロイしたり、アプリのホスティングサービスで常駐プロセスとして起動するのがよいと思います。

ソケットモードは、企業内でのユースケースでいうと、公開エンドポイントを用意することが必要なくなったことが最も嬉しい点ですが、ソケットモードの登場によって、個人的にちょっと試してみるときにも Slack アプリ開発はかつてのようにとても手軽になったと思います。

2、3 年前と比べると Slack プラットフォームは、ブロックキット、ショートカット、ホームタブなど多くの新機能が追加されています。この機会にぜひ試してみてください :wave:

ドキュメント

関連するドキュメントのリンクです。

日本語の情報は以下を参考にしてください。

以下はこの記事投稿時点で英語のみとなります。

seratch
Slack で Bolt などの Slack 公式 SDK の開発、日本での API に関する啓蒙活動を担当しています。
http://git.io/sera
slack
Slack は必要なメンバーから情報、ツールまで一元化するメッセージプラットフォームと、そのアプリ開発用プラットフォームを提供しています。
https://api.slack.com
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away