ソケットモード(Socket Mode)とは?
Slack は、2020 年に開催された年次イベント Frontiers にて「ソケットモード」という Slack アプリの新しい通信方式を発表しました。
これまでの Slack アプリ開発では、イベント API やモーダル送信、ボタンクリック、スラッシュコマンドなどのインタラクティブな操作のハンドリングには、公開された Web エンドポイントを用意して Slack からのリクエストを受信する必要がありました。
ソケットモードは、これを WebSocket での接続に切り替えることができる機能です。昔から Slack アプリやボットに慣れ親しんでいる方であれば Hubot や RTM (Real Time Messaging) API のように接続できる方式という言い方がわかりやすいかもしれません(RTM との違いについては、こちらのコメントを参考にしてください)。
ソケットモード GA リリース
そのソケットモードがベータ期間を経て 2021年1月12日(米国時間)に正式リリースとなりました! 英語となりますが、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 に遷移してください。この画面にくるとすでに Interactivity は On になっています。そして Request URL を設定する必要がない旨、表示されています。以下の Create New Shortcut ボタンからショートカットを設定しましょう。
今回の例ではグローバルショートカットを使いますので、そのまま Next をクリックしてください。
Name や Short 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_enabled
や all_message_trace_enabled
を True
に指定してみてください。
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 イベントの受信やグローバルショートカットの実行を試してみてください。
Go ではじめる
Slack ソケットモードの最も簡単な始め方 (Go 編) を参考にしてみてください。
次のステップ
ある程度動くようになったら、デプロイをどうするかを検討してみてください。
WebSocket で接続するアプリですので FaaS (Function as a Service) のようなソリューションはマッチしないでしょう。Docker イメージを作って ECS や Lightsail Containers などにデプロイしたり、アプリのホスティングサービスで常駐プロセスとして起動するのがよいと思います。
ソケットモードは、企業内でのユースケースでいうと、公開エンドポイントを用意することが必要なくなったことが最も嬉しい点ですが、ソケットモードの登場によって、個人的にちょっと試してみるときにも Slack アプリ開発はかつてのようにとても手軽になったと思います。
2、3 年前と比べると Slack プラットフォームは、ブロックキット、ショートカット、ホームタブなど多くの新機能が追加されています。この機会にぜひ試してみてください
ドキュメント
関連するドキュメントのリンクです。
日本語の情報は以下を参考にしてください。
以下はこの記事投稿時点で英語のみとなります。