Node-REDでSlackのSocket Modeを使ってみた

SlackのSocket Modeを使ってNode-RED上でSlackのイベントを取得するノードを実装したので、その紹介記事になります。

## 想定読者

* Slackの投稿内容などを活用したアプリケーション（Botや他のツールとの連携など）の開発を考えている方。
* Node-REDの動かし方やNode-RED上での実装方法（ノードの配置やワイヤの結線など）をある程度知っている方。
    * Node-REDの知識がない方は以下をご参考にどうぞ。
        * https://nodered.jp/docs/getting-started/
        * https://nodered.jp/docs/tutorials/
        * https://qiita.com/egplnt/items/222a3ae289472bb759b1
            * 少し古い記事なのでUIなど一部最新のものと異なっている場合があります。

## やったこと

Slackで[Socket Mode](https://api.slack.com/apis/connections/socket)がGAされたので、[公式のSDK](https://github.com/slackapi/node-slack-sdk)を使ってNode-REDでSocket Modeを使った[Events API](https://api.slack.com/events)用のノードを実装＆公開しました。

* GitHub：https://github.com/tkscode/node-red-contrib-slack-socket
* npm：https://www.npmjs.com/package/node-red-contrib-slack-socket
* Node-RED flows：https://flows.nodered.org/node/node-red-contrib-slack-socket

## 開発環境

* Ubuntu 18.04
    * 実際はWindows10上のWSL2で動かしています。
* Node.js v12.20
* Docker v20.10
    * WSL2 + Docker Desktop for Windowsで動かしています。
    * Node-REDを動かすのに利用しています。
* docker-compose v1.27

## 使い方

### Slackアプリケーションの作成

* 公式ドキュメントの"[Intro to Socket Mode](https://api.slack.com/apis/connections/socket)"を参考にして、Slack用アプリケーションの作成とapp-level tokenの生成、ワークスペースへのアプリケーションのインストールを行います。
* アプリ管理画面の`OAuth & Permissions`->`Scopes`->`Add an OAuth scope`からイベント取得に必要な権限（スコープ）を設定します。
    * Socket Modeを有効にしている場合、デフォルトで`app_mentions:read`（アプリケーション宛てのメンション）が追加されています。
    * メンションがない通常の投稿を拾いたい場合は`channels:history`（Publicチャンネルの投稿取得）や`groups:history`（Privateチャンネルの投稿取得）を追加します。
* アプリ管理画面の`Event Subscriptions`->`Subscribe to bot events`->`Add Bot User Event`から取得するイベントの種類を選択します。
    * メンションを取得するイベントは`app_mention`、Publicチャンネルの投稿イベントは`message:channels`、Privateチャンネルの投稿イベントは`message:groups`を選択します。

### Node-REDの起動とノードの追加

* [Node-REDの公式ドキュメント](https://nodered.jp/docs/getting-started/)を参考にNode-REDを起動します（今回はDockerで起動しています）。

```
$ docker volume create nodered-test-vol
nodered-test-vol
$ docker run -d -p 1880:1880 -v nodered-test-vol:/data -v /etc/localtime:/etc/localtime:ro --name nodered-test nodered/node-red:latest-12-minimal
Unable to find image 'nodered/node-red:latest-12-minimal' locally
latest-12-minimal: Pulling from nodered/node-red
0a6724ff3fcd: Already exists
5fd2bdfdbf4b: Already exists
80b224d472a8: Already exists
e21405c347ae: Already exists
b6afffd6ee9d: Pull complete
78320e61ab74: Pull complete
ba2a0afb7fb7: Pull complete
09f8757c9445: Pull complete
db061e81eb3b: Pull complete
3c59eb56174f: Pull complete
a11846f67ffa: Pull complete
0b0bc0c51c26: Pull complete
Digest: sha256:4b7d40ab5aa0fe307f4dc7b10c6aa0da47f7001bd41e1ff794dde07bd351534e
Status: Downloaded newer image for nodered/node-red:latest-12-minimal
d5eb6de063b2c8e1f6523bccf9f91c1c30acb6e7d3054c435c88a0171c9ddb83
```

* ブラウザで http://localhost:1880 にアクセスし、Node-REDの実装画面を開きます。
    * 上記のDockerを使った方法では以下のような警告コメントが表示されますが、無視してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1062325/7d63e8ca-b7fe-6a19-52e3-2c1c188c26cd.png)

* 画面右上のメニュー展開ボタンをクリックし、`パレットの管理`->`ノードを追加`で検索バーに`node-red-contrib-slack-socket`を入力し、出てきた項目の`ノードを追加`ボタンをクリックします。（確認のポップアップが表示されるので`追加`をクリックします）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1062325/c4741b71-1da8-86bf-14a5-715190604c8f.png)

* ノードのインストールが完了したら（「ノードをパレットに追加しました」というポップが表示されたら）画面左側のノードが羅列されているエリアの下の`chat`セクションに`Slack Listen`があることを確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1062325/c829e909-a3c9-a41d-29ad-146dd29bd1e6.png)

### ノードの設定

* 画面中央に`Slack Listen`ノードを配置（ドラッグ＆ドロップ）し、配置したノードをダブルクリックして設定画面を表示します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1062325/9065b802-00c5-1cdf-d37d-fe0b3297719d.png)

* 設定画面の`Settings`の行にある編集アイコン（鉛筆アイコン）をクリックしてトークンや取得するイベントの指定画面を表示します
* 以下の通り情報を入力します。
    * `Name`：設定の名前（なんでもOKです）
    * `Token`：Slackアプリケーションを作成したときに生成したapp-level token
        * OAuthトークンではなく、`xapp-`から始まるトークンになるので注意してください。
        * なお、デフォルトではトークンを直接入力するようになっていますが、[Node-REDのglobalContext](https://nodered.jp/docs/user-guide/context)や環境変数から指定できるようにもなっています。
            * 例えば、環境変数から指定する場合は入力欄左のアイコンをクリックして`$ 環境変数`を選択し、環境変数名（`SLACK_APP_TOKEN`など）を指定します。
    * `Events`：取得するイベント名
        * イベント名は[SlackのAPI Event Types](https://api.slack.com/events)にある`Works with`列に`Event API`と記載があるもの（例：`app_mention`や`message`など）を指定します。
        * 複数のイベントを指定する場合は`app_mention,message`のようにカンマ区切りで指定します。
* 設定画面右上の`追加`->`完了`をクリックします。
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1062325/adae3686-49f5-d1cf-c5eb-272415288973.png)

### Slackイベントの取得

* `Slack Listen`ノードに`debug`ノードを接続し、画面右上の`デプロイ`ボタンをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1062325/b32c2548-c088-1d1b-63c3-28a3dd0d42cd.png)

* `Slack Listen`ノードの下に`connected`と表示されることを確認したらSlackからアプリケーション宛てにメンションでメッセージを送ります。
    * `connected`と表示されない場合は、Socket Modeが有効になっているか、トークンが有効なものか（Revokeされていないか、app-level tokenかなど）を確認してください。
* 画面右側にあるデバッグアイコン（虫のアイコン）をクリックし、`Slack Listen`ノードで取得したイベント情報（前項で送られたメッセージの情報）が`debug`ノードから出力されていることを確認します。
![キャプチャ2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1062325/16922c02-3a4f-75fb-e0aa-1fa28a305b96.png)

**`Slack Listen`ノードでは取得したイベント情報をそのまま出力しているので、出力内容を事前に知りたい場合は[Slackの各Event API](https://api.slack.com/events)の試用を参照してください。**

また、取得したイベント情報の加工などを行う場合は適宜`change`ノードや`function`ノードなどをつないで処理を実装してください。（以下の例ではアプリケーション宛てのメンションからメッセージ部分のみを取得しています）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1062325/2ea8bc9f-0a57-2e32-2186-e569fc21179d.png)

## その他

イベントが取得できない場合は以下を確認してみてください。

* `Slack Listen`ノードの下に`connected`と表示されているかどうか
    * `connected`以外の場合は設定したトークンが正しいものかどうか確認してください
* Slackのアプリ管理画面（`OAuth & Permissions`->`Scopes`）にてイベント取得のための権限が十分かどうか
* Slackのアプリ管理画面（`Event Subscriptions`->`Subscribe to bot events`）にて取得可能なイベントがちゃんと指定されているかどうか

## 参考

* https://api.slack.com/apis/connections/socket
* https://slack.com/intl/ja-jp/blog/transformation/people-partners-systems-slack
* https://medium.com/slack-developer-blog/socket-to-me-3d122f96d955
* https://qiita.com/seratch/items/1a460c08c3e245b56441
* https://github.com/slackapi/node-slack-sdk