歴史的経緯によって発生した Slack API での命名・リクエストパターンの解説

Slack の公式 SDK 開発と日本の Developer Relations を担当している瀬良 (@seratch) と申します :wave: 

この記事では、日本のみならず、たまにご質問を受ける **Slack API における直感的でない API レベルでの名称等** について解説をしていきたいと思います。この記事は、主に Slack の歴史の中でサービス内の名称が途中で変わった、などの歴史的経緯に起因した分かりにくいものを中心に取り上げています。

### プロパティの命名や細かい注意点の解説

まず、API のパラメーターや応答、Events API やインタラクティビティのペイロードでよく出てくるプロパティ名で直感的に分かりにくそうなものを列挙していきます。

|名称|概要|
|---|---|
|user_id|Slack ユーザーの ID。ユニバーサルにユニークではなく、ワークスペース・OrG 内でユニークとなる。かつては `U` ではじまる場合と `W` ではじまる場合があったが、新しく作られるものは全て `W` はじまりとなっている。|
|team_id|ワークスペースの ID。`T03E94MJU` のような T 始まりの文字列。|
|enterprise_id|Enterprise Grid の OrG の ID。`E013Y3SHLAY` のような E 始まりの文字列。|
|app_id / api_app_id|Slack アプリの ID。`A1NATCVAM` のような A 始まりの文字列。これは https://api.slack.com/apps で作成したアプリごとに作成され、ワークスペース単位ではない。|
|bot_id|Slack アプリのあるワークスペースにおけるボットユーザーの ID。`B16UWMK9P` のような B 始まりの文字列。アプリがインストールされたワークスペースごとに異なる値になる。この ID は [`bots.info` API](https://api.slack.com/methods/bots.info) に渡してそのボットユーザーの情報を得るために利用される。ボットユーザーはユーザー ID も持っており、そちらの方が利用するシーンは多い。ユーザー ID やアプリの ID は bots.info の応答や各種ペイロードの `bot_profile` の中に含まれる。|
|ts|あるチャンネルの中でのメッセージの投稿日時を示す UNIX タイムスタンプをベースにした値。`1655933772.093149` のような形式になる。「`.`」以降のパートは、全くの同一タイミングでメッセージが投稿されたときに重複しないよう、値が設定されるようになっている。そのため、人間が見るための投稿日時の計算には小数点以下を切り捨てて計算するとよい。なお、メッセージを一意に特定するには `channel_id` と `ts` の組み合わせを指定する。|
|blocks|[Block Kit](https://api.slack.com/block-kit) のリッチでインタラクティブな UI を表現するためのブロックの配列。多くのブロックはチャンネルメッセージ、モーダル、ホームタブで再利用することが可能。ボタンなどのインタラクティブなブロックを置く場合は、クリック等のイベントを後述の "block_actions" というペイロードパターンとして応答する必要がある。|
|attachments|メッセージの属性の一つで、ファイルやメールの添付、カラーバーの表現などに使われる配列。[Block Kit](https://api.slack.com/block-kit) が登場する前は "attachments" が[唯一のリッチなメッセージを構築するための方法](https://api.slack.com/interactive-messages)だった。現在は "blocks" を代わりに使うことが推奨されている。|
|cursor|ページングでの取得が必要となる API の場合は `response_metadata.next_cursor` という値が返される。`bmV4dF90czoxNjU1OTMzNjQ4NDMyMTM5` のような値になる。この文字列を `cursor` パラメーターとして指定して次のページを取得する。なお、値が空文字の場合は次のページがないことを意味する。|
|channel|基本的にはパブリックチャンネルを意味する。なお `conversations.*` API の応答は、旧来の API からのスムーズな移行を想定して `conversations` と名称を変更せず `channels` という名前でチャンネルのリストを返しているため、このリストは `channels` という名称でありながら、プライベートチャンネルや DM が含まれうる。|
|group|プライベートチャンネルを意味する。かつてはプライベートチャンネルの ID は `G` 始まりだったが、現在は新しく作られるものはパブリックと同様、全て `C` 始まりとなる。|
|im|1:1 の DM (ダイレクトメッセージ)を指す。IM は Instant Messaging の略称。|
|mpim|複数人での DM を指す。|
|shared|Slack コネクトのチャンネルは、当初、共有チャンネル（Shared Channel）と呼ばれていた。そのため、API レベルでは Slack コネクト関連のものは "shared" という形容詞がつくことが多い。"slack_connect" という語は API レベルでは出てこない。ただし、接続した他のワークスペースを意味する `connected_teams` という語は統一的に利用されている。|
|remote file|remote file は Slack にアップロードされたファイルではなく、Google Drive や Box など別のストレージサービスにアップロードされてアクセス可能な URL を持つものを論理的に Slack のファイルと同様に扱うもの。検索インデックスにキーワード付きで登録したり、チャンネル内でのプレビュー画像を指定したりする。|
|stars|現在の UI では、ブックマーク（Saved Items）と呼ばれるもの。かつては Starred Items だったため、`stars` となっている。なお、最近追加された `bookmarks.*` API の `bookmark` は、チャンネルのブックマークアイテムを指すので別物。|
|subteam|たまに出てくる名詞だが 3rd party からの利用としては、ユーザーグループと同義と考えて支障ない。ユーザーグループは、名残から `S03MGJJP5QQ` のような S 始まりの ID を使っている。|
|is_stranger|Slack コネクトで他のワークスペースとつながっている場合に、ユーザー情報の詳細を取得できない相手ワークスペース側のユーザーを指す。|
|is_workflow_bot|ワークフロービルダーで作られたワークフローによるメッセージ投稿の投稿者となるボットユーザーを指す。|
|is_restricted|ゲストユーザーを意味する。|
|is_ultra_restricted|シングルチャンネルゲストを意味する。|
|is_primary_owner|ワークスペースのプライマリーオーナーを意味する。他のロールとの違いなど詳細は[ヘルプページ](https://slack.com/intl/ja-jp/help/articles/360018112273)を参照。|
|is_owner|ワークスペースのオーナーを意味する。他のロールとの違いなど詳細は[ヘルプページ](https://slack.com/intl/ja-jp/help/articles/360018112273)を参照。|
|is_admin|ワークスペースの管理者を意味する。他のロールとの違いなど詳細は[ヘルプページ](https://slack.com/intl/ja-jp/help/articles/360018112273)を参照。|
|dialog|現行のモーダルダイアログではなく、旧来の静的なダイアログを指す。現在でもこちらは利用可能だが、モーダルの利用が推奨されている。|
|view|モーダルやホームタブの UI を表現するデータ。views.open / views.update / views.push API でモーダル、views.publish API でホームタブを操作する。|

### Slack から送信されるリクエストパターンの種別

どれくらいの機能を使うかにもよりますが、ご自身が開発・運用するアプリに対して Slack からデータが送信されるパターンがいくつかあります。

これらのパターンで送信される最新のペイロード種別を理解するには、ソケットモードの 3 つのパターンを見るのがわかりやすいでしょう（なお、Outgoing Webhooks など現在は推奨されていない旧来の仕組みは下で別途まとめてあります）。

|type の値|概要|
|---|---|
|"events_api"|Events API のペイロード。Slack ワークスペース / OrG 内でイベントが発生したときにそのデータを通知として受信する。|
|"interactive"|グローバル/メッセージショートカットの実行、メッセージ内のボタンクリック、モーダルからのデータ送信、外部データを使った検索セレクトメニューへの検索リクエストなどのユーザーとのインタラクションによって送信されるペイロード。|
|"slash_commands"|`/feedback` のようなスラッシュから始まるコマンド。チャンネルのメッセージ送信エリアから打ち込むことで送信できる。|

ソケットモードで受け取る場合は、常に以下のような JSON データの形式となっています。

```json
{
  "envelope_id": "リクエストごとにユニーク",
  "type": "events_api / interactive / slash_commands",
  "payload": { }
}
```

一方、これらをソケットモードではなく HTTP エンドポイントで受信する場合、それぞれデータの形式が異なっています。これは、それぞれの機能のリリース時期が大きく異なり、その際にそれぞれ別の仕様として実装することが意志決定された結果です。

公式 SDK を使わずに自前で実装する必要がある場合はご注意ください。参考までに[公式 Python SDK の実装](https://github.com/slackapi/bolt-python/blob/v1.14.0/slack_bolt/request/internals.py#L31-L46)に日本語での補足コメントをつけて引用しておきます。

```python
# body が文字列型前提なのは 2022 年現在 Slack から送信されてくるペイロードはバイナリデータを含むことがないため
def parse_body(body: str, content_type: Optional[str]) -> Dict[str, Any]:
    if not body:
        # 正常でないリクエスト（= Slack からのリクエストでない）の場合
        return {}
    if (content_type is not None and content_type == "application/json") or body.startswith("{"):
        # Events API の場合は JSON のリクエストボディが送信される
        return json.loads(body)
    else:
        if "payload" in body:
            # インタラクティビティの場合は application/x-www-form-urlencoded のボディの中に
            # payload というキーで含まれるデータが JSON のデータとなっているので
            # これを URL デコードした上で JSON としてロードする必要がある
            params = dict(parse_qsl(body))
            if params.get("payload") is not None:
                return json.loads(params.get("payload"))
            else:
                return {}
        else:
            # payload キーを含まない場合はスラッシュコマンドか
            # url_verification / ssl_check などの通信チェック用のリクエストとなる
            # スラッシュコマンドはトップレベルで全てのパラメーターが含まれているのでそのまま読む
            return dict(parse_qsl(body))
```

次にそれぞれのペイロードパターンについてもう少し細かくまとめておきます。

* イベント API (Events API)
* インタラクティビティ
* スラッシュコマンド

#### イベント API (Events API)

Events API で送信されうるイベントの種別は https://api.slack.com/events?filter=Events を参照してください。https://api.slack.com/apps の管理画面の Event Subscriptions というページで追加して、かつ必要な権限と共にワークスペースにインストールされると、指定した Request URL またはソケットモードの WebSocket コネクションに対してイベントのペイロードが送信されるようになります。

これに似たものとして RTM や Outgoing Webhooks という名前を聞いたことがあるかもしれません。簡単に説明しておきます。

* [RTM (Real Time Messaging) API](https://api.slack.com/rtm) - これは現在は新規での利用が推奨されていない旧来のイベント通知の仕組みです。ほぼ同様のことをよりスケーラブルに実現するためにはソケットモード + イベント API の利用を推奨しています。[こちらのコメント](https://qiita.com/seratch/items/1a460c08c3e245b56441#comment-44354816240098892ca8)もあわせて参考にしてみてください。
* [Outgoing Webhooks](https://api.slack.com/legacy/custom-integrations/outgoing-webhooks) - 旧来のカスタムインテグレーションで現在は利用を推奨していません。同様のことはイベント API で[メッセージイベント](https://api.slack.com/events/message)を受信した上で、その内容をキーワードでフィルタリングすることで実現することができます。

#### インタラクティビティ

インタラクティビティには、さまざまなパターンがあります。

[Bolt フレームワーク](https://api.slack.com/bolt)を使っている場合は、旧来の非推奨となったもの以外は全て対応していますが、自前で実装する場合は必要なものだけをハンドリングする方がコードがシンプルになるでしょう。

パターンの一覧にいく前に "input" と "input" ではないブロックの使い分けはよくご質問をいただくポイントなので先に解説しておきます。これらのブロックの違いは以下の通りです。

* "input" ブロックの入力は、基本的にモーダルの Submit ボタンを押したときの一括送信のデータに含まれることを想定している
* 一方で "input" でない "section" / "actions" ブロックのボタンやセレクトメニューのインタラクションのデータ送信は一つずつ都度送信となる

これらを併用することで、例えば・・

* モーダルの初期状態ではカテゴリを選択する "actions" / "section" ブロックだけを置いておき、標準の Submit ボタンを表示しないようにしておく
* カテゴリが選択されたら、モーダル全体を書き換えて一括送信したい "input" ブロックの一覧と Submit ボタンのあるビューを表示する

というような、よりインタラクティブな画面遷移の体験を実装することができるようになります。

過去にこれを活用する実装例を紹介したことがありますので、ご興味あれば観てみてください。

https://qiita.com/seratch/items/9b5666479ad9f602e5ee

それでは、パターンの一覧を見てみましょう。

|type の値|概要|
|---|---|
|"block_actions"|Block Kit の "input" type 以外のインタラクティブなブロック（ボタンやセレクトメニュー）で、クリックやアイテム選択が行われたときに発火する。具体的には "section" / "actions" ブロック内でのイベントとなる。ただし、"input" ブロックの場合でも `dispatch_action: true` を指定した場合は後述の "view_submission" に含まれるだけでなく、インタラクション直後にこの "block_actions" **も** 送信することができるようになる。なお、当初は将来の拡張を考慮して、種別が複数形かつペイロードの中も "actions" という配列が含まれているが 2022 年時点でも一度に送信される action は一件だけである。|
|"block_suggestion"|これは "external_select" という Slack 内ではなく連携する外部サービス側のデータを使ってセレクトメニューの選択肢を表示するためのブロックを使った場合に、ユーザーが検索キーワードを入力したタイミングで受信するペイロード。これを受け取ったアプリは 3 秒以内に 100 件以下の検索結果を組み立てて応答する必要がある。なお、このリクエストは Interactivity & Shortcuts 設定ページの Request URL ではなく、ページ最下部にある Select Menus > Options Load URL の URL に送信されることに注意（同じ URL を指定するのは OK）。|
|"shortcut"|グローバルショートカットが実行されたときに受信するペイロード。グローバルショートカットは有効にするときに "callback_id" という ID 文字列を指定するが、その ID と共にどのワークスペースで誰が実行したかなどの情報が提供される。|
|"message_action"|メッセージショートカットが実行されたときに受信するペイロード。type が "shortcut" ではなく "action" という名称になっているのは、リリース当初、まだグローバルショートカットは存在せず、その当時の機能名が「メッセージアクション」だったため。すでに存在するアプリケーションへの互換性維持のために type は "message_action" のままとなっている。|
|"view_submission"|モーダルの最下部にある標準の Submit ボタンを押したときに送信されるペイロード。そのモーダルに指定しておいた "callback_id" に紐づけて、送信者の情報、"input" ブロックの入力内容や private_metadata が提供される。モーダルの利用の注意点は[こちらの記事](https://qiita.com/seratch/items/0b1790697281d4cf6ab3)を参照のこと。|
|"view_closed"|モーダルを構築するときに `notify_on_close: true` という属性をつけておくと、ユーザーが送信ボタンを押す前に離脱してモーダルを閉じたタイミングでこの種別のペイロードが送信される。詳しくは[こちらの記事](https://qiita.com/seratch/items/0b1790697281d4cf6ab3)を参照。|
|"workflow_step_edit"|ワークフロービルダーから送信されるイベント。自前にカスタムステップを提供する場合、そのステップの設定画面を開いたときに送信されるペイロード。これに応答する形で表示するモーダル UI の情報を返す。なお、その設定モーダルで送信ボタンが押されたときは "view_submission" のペイロードが送信されるので、適切な "callback_id" をつけておき、カスタムステップの処理の一部としてハンドリングする（それなりに複雑になるので Bolt を使うことを推奨します）|

ここから先は、旧来の仕組みで現在は新規の利用は非推奨ですが、網羅するために説明しておきます。

|type の値|概要|
|---|---|
|"interactive_message"|["attachments" 内のボタンクリックなどのアクションが発生した場合](https://api.slack.com/legacy/interactive-messages)に送信されるペイロード。上の Block Kit やモーダルの仕組みよりも以前に実装されたもの。現在は Block Kit の UI から "block_actions" イベントを発生させるインタラクションが推奨されている。当時は Block Kit とは異なり、メッセージ上のインタラクションのみだったので、このような名称となっている。|
|"dialog_submission"|モーダル以前の旧来のダイアログのデータ送信イベント。機能は限定的だが、内容としては "view_submission" と同等。|
|"dialog_suggestion"|モーダル以前の旧来のダイアログ上での外部データを使ったセレクトメニューでの検索操作の送信イベント。機能は限定的だが、内容としては "block_suggestion" と同等。|
|"dialog_cancellation"|モーダル以前の旧来のダイアログで、ダイアログが閉じられたことを伝えるデータ送信イベント。機能は限定的だが、内容としては "view_closed" と同等。|

上記を全部読んだ方がどれくらいいるか分かりませんが・・見ての通り、"interactive_message" + "message_action" と旧来の "dialog" という仕組みがまず最初に開発された後、これら全てを横断的に扱える仕組みに改良したものとして Block Kit が出てきた、という歴史だったのでした。

#### スラッシュコマンド

基本的には、[こちらの英語ドキュメント](https://api.slack.com/interactivity/slash-commands)をみていただくのがよいかと思います。

スラッシュコマンドでは「引数として渡した文字列である `text` パラメーター内のユーザーメンション等が適切に展開された状態で送信されてこないので不便」というご指摘を非常に多くいただきますが、この挙動は残念ながら変更される予定がありません。

回避策としては、以下の issue で私が提案しているように Block Kit を使ってユーザーを選択してもらう UI に誘導するなどの対応をお願いしています。

* https://github.com/slackapi/bolt-js/issues/1361#issuecomment-1062585707
* https://github.com/slackapi/bolt-js/issues/1285

### 終わりに

Incoming Webhooks や chat.postMessage API でシンプルな通知だけを行なっている、あるいは、[Bolt フレームワーク](https://api.slack.com/bolt)を使っているといった方々には必要ない情報も多かったかもしれませんが、この辺を理解する必要が生じた方々に少しでもお役に立てば幸いです。

「私の頭の中にはあるけれど、ドキュメントにはあまり書かれていない・・」ということはまだあるように思います。例えば Enterprise Grid の管理者向けの機能は、ここ数年でかなり拡充されてきましたが、まだまだ公式の日本語情報が少ないことは認識しております。

今後は、本来、このような情報を網羅しているべき公式の英語ドキュメントの方も可能な範囲で改善をしつつ、今回のような上級利用者の方向けの解説記事も少しずつ出していければと思います。

それでは！