Slack App + Botkit4 + TypeScriptでbotを作る

# 概要

以下の技術構成で、Single Teamな（一つのワークスペースでのみ稼働する）Botを実装する方法を紹介します。

- Slack App
    - Event APIを使用します。
- Botkit 4
    - Nodeはv12.7.0を使用します。
- TypeScript

サーバへのデプロイはこの記事のスコープ外です、ローカルで立ち上げたBotプロセスとSlackが繋がるところまでを記載します。

## Slack App


Slackに自作の機能を連携させる方法の最新版は`Slack App`です。

`Custom Integration`はdeprecatedなので、今から新しく作る場合は`Slack App`を作りましょう。


### Slack Appの種類

**Slack App自体はSlackに機能を連携させるための汎用的な箱で、作成したSlack AppにBotユーザー機能だったりSlash Command機能だったりを追加してアプリケーションを作ります。**


ちなみに、提供したい機能にとって本当にBotユーザーが必要かどうかは一度考えた方が良いと思います。
ただ単にSlackから何かしらの機能を実行したいだけであればSlach Commandで事足りることも多いと思います。

~~このページ書きながら「あ、実装しようとしてた機能、Slach Commandでいいじゃん」って思った。~~


## Botkit 4

Botkit4のSlackに関する公式ドキュメントはこちら

https://botkit.ai/docs/v4/reference/slack.html

左上のBotkit versionを変更すると古い書き方も見れます。

### Botkit4での書き方とAdapter

Botkit4からは、Botの機能をAdapterという単位に切り出し、必要なAdapterをimportして使う方法になっています。
SlackのBotを作る際に必要なアダプタは `botbuilder-adapter-slack` です。

このアダプタには以下の２種類のモードが存在します。

1. **Single Team**モード
    - 単一のワークスペースでのみ使えるモード。
    - 社内のSlackでだけ使えるBotとかの場合はこっち。
2. **Multi Team**モード
    - 複数のワークスペースで使えるモード。
    - サービスとして色んなワークスペースで使ってもらう想定の場合はこっち。

今回の目的は1なので、1の方で書きます。


# 1. Slack Appを作成する

## 1.1 Slack Appを作成する

https://api.slack.com/apps

にアクセスし、 `Create New App` ボタンを押します。
アプリケーションの名前と使用するワークスペースを選択します。

### Signing Secretをコピーする

そうすると App Credentials という欄に色々重要っぽい値が払い出されてるのが分かります。
その中の `Signing Secret` の値をコピーしておきます。


## 1.2 Bot Userを作成する

さて、Botを作るためにまずはBotユーザーを追加しましょう。

下記の画面で、Slack Appに機能を追加できるので、 `Bots`をクリックします。
（この画像はこの作業が完了した後にスクショしたので既に緑になっていますが、最初は全て灰色のはずです）

<img width="690" alt="スクリーンショット 2019-11-11 0.01.50.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132179/08726b25-a2b1-e4b3-78e2-bb70416b867e.png">

<img width="708" alt="スクリーンショット 2019-11-10 23.59.50.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132179/76d0fb3a-11a5-8559-4d7c-ea5d294d5b4d.png">

:arrow_up: このボタンを押す。

<img width="708" alt="スクリーンショット 2019-11-11 0.00.59.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132179/b683dd24-a86c-dada-05d9-869100c8b21f.png">

と、こんな画面:arrow_up:になるので、Botの名前を入れましょう。
今回の実装ではRTM APIを使わないので`Always Show My Bot as Online`はONで良いです。

saveが完了したら、このBot Userをワークスペースに招待します。
下記のメニューをクリックで開いて、`Install App to Workspace`ボタンを押します。

<img width="759" alt="スクリーンショット 2019-11-11 0.09.28.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132179/6ab43cd7-4349-d900-bcb3-b8007978d829.png">

そうするとよくあるOAuthログインみたいな感じの画面が開くので、「許可する」を押します。

この時点でワークスペースにBot Userが追加された状態になるので、確認しましょう💡

<img width="478" alt="スクリーンショット 2019-11-11 0.17.17.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132179/3d24096c-62e7-f210-e101-12ff5182ecfe.png">

これは使うチャンネルにinviteした図です。Bot Userとして参加してくれてます🤖

### Bot Tokenをコピーする

ここで作成したBotに色々喋らせるので、このBotを識別する必要があります。
そのために、Permissionsをクリックし、表示される `Bot User OAuth Access Token` の値をコピーしておいてください。
二つ出てくると思うのですが、`xoxb-`から始まる方です。

こいつをBotkit的には Bot Token と呼びます。

# 2. TypeScriptの用意をする

ではやっとコード書き始めます。
もう型のないjsなんて書けないよ！ということでTypeScriptで書きます。

```
$ npm install --save-dev typescript
```

Botkitのコードの場合、サーバのnodeで走らせるのでwebpackとかは不要です。

TypeScriptのコンパイルに必要な`tsconfig.json`を作ります。

```tsconfig.json
{
  "compilerOptions": {
    "module": "esnext",
    "target": "esnext",
    "moduleResolution": "node"
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}
```

tsファイルは`src/`配下に置いて、`dist/`にjsファイルを吐き出すとしましょう。

```package.json
"scripts": {
  "start": "node dist/index.js",
  "build": "npx tsc --outDir ./dist ./src/index.ts"
}
```

こんな感じ。

これでTypeScriptの準備は終わりです。


# 3. Botkitのコードを書く

```
$ touch src/index.ts

$ npm install --save botkit botbuilder-adapter-slack
```

```index.ts
import { Botkit } from "botkit";
import { SlackAdapter, SlackEventMiddleware } from "botbuilder-adapter-slack";

const adapter = new SlackAdapter({
  clientSigningSecret: process.env.CLIENT_SIGNING_SECRET,
  botToken: process.env.BOT_TOKEN,
  // Single Teamモードでは不要なパラメータなのですが、型で必須と言われてしまうので仕方なく書いてます。
  redirectUri: ""
});
// Slackのイベントを受け取れるようにします。
adapter.use(new SlackEventMiddleware());

const controller = new Botkit({
  adapter: adapter,
});

controller.on("app_mention", async (bot, message) => {
  await bot.reply(message, "I received an app_mention event.");
});
```

ここで、Slack Appを作った時にコピーしていた２つの値を使います。
`Signing Secret`と`Bot Token`の二つです。

僕の場合はdirenvを使っているので`.envrc`に追記します。

```.envrc
export CLIENT_SIGNING_SECRET=hogehoge
export BOT_TOKEN=piyopiyo
```

# 4. コンパイルして走らせる。

`package.json`に記載済みなので、

```
$ npm run build
$ npm start
```

だけです。

ちなみにここで`Signing Secret`が空だったりすると

```
****************************************************************************************
* WARNING: Your bot is operating without recommended security mechanisms in place.     *
* Initialize your adapter with a clientSigningSecret parameter to enable               *
* verification that all incoming webhooks originate with Slack:                        *
*                                                                                      *
* var adapter = new SlackAdapter({clientSigningSecret: <my secret from slack>});       *
*                                                                                      *
****************************************************************************************
>> Slack docs: https://api.slack.com/docs/verifying-requests-from-slack
```

こういうWarningが出たり、`Bot Token`が間違ってたりすると

```
Error: An API error occurred: invalid_auth
```

というエラーが出たりします。

```
Enabling plugin:  Slack Adapter
Webhook endpoint online:  http://localhost:3000/api/messages
```

だけ出ていれば成功です。

# 5. ngrokでローカルのサーバを公開

さて、これでBotのサーバが立ち上がってる状態なわけなんですが、Event APIでの実装なので、実際に試すにはこのサーバの公開されたエンドポイントが必要です。
でも、ローカルで立ち上げている場合はそんなものあるはずもなく...

ということで[ngrok](https://ngrok.com/)を使いましょう。
日本語だと、こちらのmininobuさんの記事で概要が掴めます。

https://qiita.com/mininobu/items/b45dbc70faedf30f484e

ngrokの導入は他の方の情報にお任せするとして、先ほどのサーバを立ち上げた状態で、別プロセスでngrokします。

```
$ ngrok http 3000
```

そうするとローカルのマシンのポート3000番が公開されてURLが作成されます、最高〜〜〜

# 6. Event SubscriptionsをONにする。

ここで一度Slack Appに戻ります。

## RTM APIとEvent API

SlackのBotの実装方法は何種類かあり、今回はEvent APIを使用する方法で作成します。
これについてはnamutakaさんのこちらの記事を読むと理解しやすいです。

https://qiita.com/namutaka/items/233a83100c94af033575

また、Slackの公式のブログで以下のようにEvent APIの使用を推奨しています。

https://medium.com/slack-developer-blog/getting-started-with-slacks-apis-f930c73fc889

> Generally, we recommend using the Events API, except for in a few specific cases (for instance, for an internal integration behind a firewall). When it comes down to it, the RTM API sends a lot of unnecessary information to your server over the connected WebSockets. This is particularly important if you have multiple connected clients, or just a large team using your app.

## 6.1. SlackにURLを登録する

さて実際の操作としては、まずEvent Subscriptionsをクリックし、

<img width="719" alt="スクリーンショット 2019-11-12 0.01.37.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132179/a3f1beab-117a-7ab1-1db2-883b326e3fdf.png">

Enable EventsをONにします。

<img width="710" alt="スクリーンショット 2019-11-12 0.02.37.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132179/56297b27-7034-1970-e40e-21d72c03d312.png">

そうするとURLを入力するフィールドが出てくるので、先ほど立ち上げたngrokのURLに、パスを追記したものを入力します。
URLを入力すると自動でリクエストが飛んで、正しいURLか判定されます。
この際に飛ぶイベントは[url_verificationイベント](https://api.slack.com/events/url_verification)で、本当なら実装して適切なレスポンスを返す必要があるんですが、Botkitはもうそこは実装されているようで、コードに何も書かなくても`Verified!`になります👏

## 6.2. イベントを登録する

今回はBotがメンションされた場合のイベントをSubscribeするとして、`Subscribe to bot events`で`app_mention`を選びましょう。

<img width="717" alt="スクリーンショット 2019-11-12 0.33.37.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132179/aaa1380c-dce6-2cea-b7b6-3adddf379da5.png">

これで`Save Changes`ボタンを押してとりあえず設定完了。

ちなみに、ngrokのプロセスは立ち上げ直すとURLが変わるので、この設定を再度する必要があります。
ローカルで検証を続けたい場合はngrokのプロセスは殺さないでください。


# 7. Slackから確認

さてここまで終わると実際にSlackで試すことが出来ます。

<img width="408" alt="スクリーンショット 2019-11-12 1.10.59.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/132179/ceaccb4e-312a-558c-c9da-9985cc1f0956.png">

わーい！

ここまで来ればもう後はなんでも出来ますね。

手順としては、Slack Appの設定でSubscribeするイベントを設定して、コード上の `controller.on` に処理を書くという感じです。
Event APIで発火されるイベントはSlack上のだいたいのアクティビティをカバーするので、基本的にSlackでやりたいと思ったことは何でも出来るんじゃないでしょうか。

# おしまい

なんだかんだそれなりにステップがある上に、SlackもBotkitも新旧の情報がネットに混在しているので中々に惑わされました。
良いBotライフを送りましょう！！

~~僕はこれから本来実装する予定だった機能をBotではなくSlash Commandで作りますが.........~~