Slack の Socket Mode API を使ってみた

@Pctg_x8です。

Slack の新しい API である Socket Mode について、KLab Tech Book Vol.7 でその存在に少しだけ触れた手前ちょっと触ってみました。 Tech Book の執筆時点ではどうやらベータ公開だったようですが、2021/1/12 から一般に使えるようになった(GA)みたいです。
https://qiita.com/seratch/items/1a460c08c3e245b56441

お試しでざっくり WebSocket 通信だけ組んだやつがこちらです: https://github.com/Pctg-x8/slack-socket-mode-test/blob/master/src/main.rs

Rust なので直接 WebSocket 通信を組んでいますが、JavaScript とかであれば公式フレームワークの Bolt がすでに Socket Mode に対応しているようなので簡単に取り扱えると思います。 また、Go 言語のライブラリではSocket Mode 実装の PullRequestがマージされており、すでに使えるようになっているようです。

Socket Mode について

Socket Mode は今まで Bot integrations で使えた RTM API のように、Events API 等を WebSocket 通信で使えるようにするものです。

機能的には Events API とほぼ同じで、従来 RTM では扱えなかった Slash Commands やインタラクティブ要素(ボタンなど)に関するイベントも取り扱うことが可能です。 今までそういったインタラクティブ要素を活用しようとした場合は別途 HTTP 通信を受け取る公開サーバを建てる必要がありましたが、Socket Mode なら WebSocket 通信を繋げば OK なので非公開なサーバや手元 PC でもリアルタイムな bot を運用可能になります。

Socket Mode を有効にする

Socket Mode の有効化については、詳しくはこちらをみてもらうとすぐなのですが、大まかにまとめると次の 3 つの手順が必要になります。

  1. Settings > Socket Mode から Socket Mode を有効にする
  2. Features > Event Subscriptions で Event Subscription を有効にする
  3. Subscribe to (bot events / events on behalf of users) で受け取りたいイベントを選択する

1. Settings > Socket Mode から Socket Mode を有効にする

Socket Mode はデフォルトで OFF になっているので、まずはこれを ON にします。

switch

2. Features > Event Subscriptions で Event Subscription を有効にする

Socket Mode で Event Subscriptions のイベントを受け取るため、Event Subscriptions も ON にします。

event_switch

3. Subscribe to (bot events / events on behalf of users) で受け取りたいイベントを選択する

例えば、チャンネルへの投稿イベントを受け取りたい場合は message.channels を指定します。

event_type

  • Subscribe to bot events ではボット発のイベントを指定します。ここを指定しないことで bot 同士による無限投稿ループは起きなくなります(のはず)。
  • Subscribe to events on behalf of users ではユーザ発のイベントを指定します。大抵はユーザの投稿に反応する形になると思うので、これだけ指定すれば OK です。

Socket Mode に接続する

Socket Mode に接続するには、その WebSocket の URL を入手する必要があります。 これは apps.connections.open を叩くことで入手できます。 https://api.slack.com/methods/apps.connections.open

Socket Mode は「最初に WebSocket の URL を入手する」という Discord の Gateway と似たフローを辿りますが、Gateway では WebSocket 接続に認証するのに対して Socket Mode では WebSocket URL を取得する API の発行タイミングで(つまり接続に)認証を行います。 Socket Mode を有効にした時に発行された App-Level Token (Bot Token や OAuth Token とは違うもので、 xapp- から始まるもの)を Bearer Token として Authorization ヘッダに指定することで App を認証します。

レスポンスは特に何も指定しなければ JSON で返ってくるので、適切なデシリアライザで分解します。

// note: 本当はurlかerrorのどちらかしか来ないので、ちゃんとokの値でvariantを選択するようなDeserializeを実装した方が良い
#[derive(serde::Deserialize, Debug)]
pub struct OpenConnectionsResponse {
    pub ok: bool,
    pub url: Option,
    pub error: Option
}

let open_connections_response: Result =
    surf::post("https://slack.com/api/apps.connections.open")
        .header(surf::http::headers::AUTHORIZATION, format!("Bearer {}", token))
        .recv_json().await;

成功時には以下のような JSON データが返ってきます。

{ "ok": true, "url": "wss://wss-primary.slack.com/link/?hogehoge..." }

何かしらエラーがあった場合は以下のようなデータが返ってきます。

{ "ok": false, "error": "error message" }

WebSocket の URL を入手できたら、あとはそれを使って WebSocket 通信を開始すれば OK です。

WebSocket 通信を開始すると、最初に hello メッセージが送られてきます。どういったデータがくるかは下記 URL をご参考ください。
https://api.slack.com/apis/connections/socket-implement#connect

切断時 or リフレッシュ時の挙動

Socket Mode が OFF になったなどで切断が必要な場合、またはセッションのリフレッシュが必要になった場合には、サーバ側が一方的に shutdown するのではなく disconnect メッセージが送られてきます。 この disconnect メッセージは上記以外でも送られてくるようですが、これが送られてきたということはいずれにしろ通信の終了が必要だということなので復帰をどうするかは置いておいてひとまず通信を終了させると良いと思います。

セッションのリフレッシュ時には、以下のように disconnect メッセージの reasonrefresh_requested と記述されているため、大まかにはこれをもって再接続か終了かを判定することになります。

{ "type": "disconnect", "reason": "refresh_requested", ... }

イベントハンドリング

message.channels のようなイベントは、Events API のペイロードの一つとして送られてきます。 Socket Mode で Events API のイベントを受け取る場合、実際のペイロードの他に envelope_id と名付けられたメッセージごとにユニークな ID がくっついてきます。

{ "type": "events_api", "envelope_id": "01234567-aabb-ccdd-eeff-xxyyzz84841111", "payload": ... }

Socket Mode でこのデータを受け取った場合、この envelope_id を使って 3 秒以内に 以下のような Acknowledge メッセージを返す必要があります。

{ "envelope_id": "01234567-aabb-ccdd-eeff-xxyyzz84841111" }

3 秒というのは従来の Events API のレスポンス期限と同じで、Events API では HTTP Response で返す代わりに Socket Mode では WebSocket のテキストメッセージで返します。 ここで Acknowledge を返送しないと、サーバ側はイベントが正しくクライアントまで到達しなかったものとして、別の envelope_id でもう一度同じイベントを再送してきます。

自分は最初ここで引っかかって、(2 回同じイベント来るけどそういうもんなのかな......?)って思っていたんですが、最初の記事の作者さんから「反応返してないのでは?」とリプライをいただいて気づきました。
https://twitter.com/seratch_ja/status/1349536043200987141

おわり

Socket Mode の触りの部分としては以上になります。上記で挙げた message.channels 以外で送られてくるイベントなどは Events API のドキュメントがそのまま参考になるかと思います。

個人的には、RTM だとクライアント側が定期的に ping を発しないといけなかったところが Socket Mode だとサーバ側からリフレッシュ要求が飛んでくる形になるので単純に再接続するだけでよく、プログラムがかなり組みやすくなったのではないかと思います。Heartbeat 専用のスレッドの管理を考えなくてもよくなるのはとても助かります。

権限設定は RTM より少しだけ複雑ですが、適切に設定すれば bot 同士の投稿合戦を特に bot プログラム側で対策しなくてもよくなる点も良いポイントかと思います。

このブログについて

KLabのゲーム開発・運用で培われた技術や挑戦とそのノウハウを発信します。

おすすめ

合わせて読みたい

このブログについて

KLabのゲーム開発・運用で培われた技術や挑戦とそのノウハウを発信します。