SlackのRTM APIと対話的に戯れてみた

# Slack RTM APIとは？
RTMは*Real Time Messaging*の略です。
その名の通り、Slackのサーバーとクライアントがリアルタイムに(かつ双方向に)やり取りするためのAPIです。web socketという仕組みが使われています。

現在は[Web API](https://api.slack.com/web)と[Events API](https://api.slack.com/web)を使うように推奨されていますが、まだ一応は使えます[^1]。

[^1]: Classic AppもしくはBots インテグレーションで発行したトークンでRTM APIが利用できます。

また、RTM APIにしか出来ないこともいくつかあります。

「Web API + Events API」と「RTM API」のどちらを使えばよいかの判断基準は[Events API FAQ](https://api.slack.com/faq#events_api)にまとめられていますが、個人的には

> You're building an on-premise integration or have no ability to receive external HTTP requests

つまり

**Slackのアプリ(例：ボット)を動かすサーバーを外部に公開したくない/できない**

ような場合、RTM APIは非常に便利です[^2]。

[^2]: ただしRTM APIを使う場合、作成したSlackアプリの配布は出来ません。個人で活用する分には問題ないと思います。

# 簡単にRTM APIを使う方法
Slack公式でPythonからRTM APIを使うためのライブラリが公開されています。

[slackapi/python-slackclient](https://github.com/slackapi/python-slackclient#basic-usage-of-the-rtm-client)

単純にボットを作るだけなら、ライブラリを使うのが手っ取り早いです。

# あえてAPIを直にたたく
冒頭でも述べたようにRTM APIはweb socketという仕組みで実現されています。
勉強のために、このweb socketを直接触ってみようと思います！

## 下準備
まずは[websocat](https://github.com/vi/websocat)というコマンドを導入します。
インストール方法の詳細は[リポジトリのREADME](https://github.com/vi/websocat#installation)を見てほしいのですが、自分の環境(Ubuntu18.04)では次のようにインストールしました

```bash
# debファイルをいい感じにインストールしてくれるコマンドを導入しておく
# debファイルがインストールできさえすれば良いので他のコマンドでも可
$ sudo apt install gdebi
# websocatのdebファイルのダウンロード
$ wget https://github.com/vi/websocat/releases/download/v1.5.0/websocat_1.5.0_ssl1.1_amd64.deb
# debファイルのインストール
$ sudo gdebi install websocat_1.5.0_ssl1.1_amd64.deb
```

また、SlackのAPIはJSONファイルを扱うことが多いので、JSON整形用のコマンドを導入しました。

```bash
$ sudo apt install jq
```

最後にRTM APIを使うためのトークンを取得します。
一番手っ取り早いのはBotsインテグレーションの利用です。

https://[your_team_name].slack.com/apps/A0F7YS25R-bots

から`Slackに追加`をクリックし、ボットのユーザー名を入力するとトークン(`xoxb-`から始まる文字列)が表示されます。


## 実践
準備が整ったので早速APIを触ってみましょう。

まずはwebsocketでRTMを開始するためのURLを発行します。
詳細は[rtm.connect](https://api.slack.com/methods/rtm.connect)を参照してください

```bash
# xoxb-***には取得したトークンを記述する
$ curl -X GET -H "Authorization: Bearer xoxb-*************" https://slack.com/api/rtm.connect | jq  
```
APIの結果はJSON形式で返ってくるので`jq`で整形しています。
実行例は以下の通りです。

```json
{
  "ok": true,
  "url": "wss://cerberus-xxxx.lb.slack-msgs.com/websocket/*****",
  "team": {
    "id": "****",
    "name": "your_team_name",
    "domain": "your_team_name"
  },
  "self": {
    "id": "*****",
    "name": "your_bot_name"
  }
}
```

この`url`の値をもとに次のコマンドをすぐに実行します(30秒以内の制限あり)。

```bash
$ websocat wss://cerberus-xxxx.lb.slack-msgs.com/websocket/*****
```

ただ、30秒以内にURLをコピーしてwebsocatコマンドを実行するのは大変なので次のようにコマンドをまとめた方がよいかもしれません。

```bash
$ export SLACK_TOKEN="xoxb-******"
$ websocat $(curl -sS -X GET -H "Authorization: Bearer ${SLACK_TOKEN}" https://slack.com/api/rtm.connect | jq -r '.url')
```

成功すると

```json
{"type": "hello"}
```
と表示されるはずです。

### イベントを眺める
Slackクライアントからボットにダイレクトメッセージを送ってみましょう。ここでは`hoge`というメッセージを送ってみました。

このときターミナル側では次のような出力が得られるはずです(一部マスクしています)。

```plaintext
{"type":"user_typing","channel":"*****","user":"******"}
{"client_msg_id":"******","suppress_notification":false,"type":"message","text":"hoge","user":"******","team":"******","blocks":[{"type":"rich_text","block_id":"tKU","elements":[{"type":"rich_text_section","elements":[{"type":"text","text":"hoge"}]}]}],"source_team":"******","user_team":"*****","channel":"*****","event_ts":"1583495285.002500","ts":"1583495285.002500"}
{"type":"desktop_notification","title":"*****","subtitle":"******","msg":"1583495285.002500","ts":"1583495285.002500","content":"hoge","channel":"******","launchUri":"slack:\/\/channel?id=******&message=1583495285002500&team=TCH2Q1N1H","avatarImage":"https:\/\/avatars.slack-edge.com\/****.png","ssbFilename":"knock_brush.mp3","imageUri":null,"is_shared":false,"is_channel_invite":false,"event_ts":"1583495286.000100"}

```

これらはSlackのイベントを表しています。
どうやら`hoge`というダイレクトメッセージを送った結果、3つのイベントが発生したようです。
どのようなイベントが発生したかは、`type`の値を見ればわかります。

- 1行目: `type`は`user_typing`となっています。このイベントは文字入力をしメッセージをているときに生じるイベントです (○○さんが入力しています みたいなメッセージが表示されることがありますよね)。
- 2行目: `type`は`message`になっています。文字通りメッセージが送られたことを表すイベントです。`text`の値も確かに`hoge`になっています。
- 3行目: `type`は`desktop_notification`です。

他にも様々なイベントが存在し[ここ](https://api.slack.com/rtm#events)にまとめられています。

### RTM APIでメッセージを送る
websocatの標準入力に文字を入力することでRTM APIを用いてメッセージを送ることができます。
ただしメッセージの送信はJSON形式で行います。

以下は入力例です。

```plaintext
{"type": "message", "channel": "******", "text": "foo"}
```

`channel`には`user_typing`や`message`イベントで受け取った`channel`の値をそのまま入力します。

うまくいくとSlackクライアント側で`foo`というメッセージがボットに送られたことを確認できるはずです。

# まとめ
websocetで直接RTM APIを叩いてみました。実際にボットを作るのであれば各言語で用意されているライブラリを使うべきでしょう。

現在推奨されているEvents APIではサブスクライブしたイベントだけがアプリサーバーに送られます。一方でRTM APIにはそういったスコープのようなものは無いです。

実際にRTMでイベントを受信するのを眺めてみると、スコープもなく様々なイベントが送られてくることが実感できますね！