Slack アプリフレームワーク Bolt for JavaScript を Cloud Functions for Firebase で動かそう

## 重要なお知らせ

Bolt for JavaScript を Google Cloud Functions や AWS Lambda で Bolt アプリを動かす際は以下のような問題に当たる可能性があります。

* [FaaS 全般に関する課題](https://github.com/slackapi/bolt/issues/361)
* [FaaS での Events API 利用に関する課題](https://github.com/slackapi/bolt/issues/395)

シンプルな応答をするだけのアプリであればおそらく問題なく動かすことができますが、Events API のハンドリング、非同期処理の開始を含むアプリの場合はご注意ください。

2020 年 6 月更新：**Bolt for JS v2 から `processBeforeResponse` というオプションが導入されました。これを有効にしていれば FaaS でも期待通りに動作するようになりました。**

---

この記事では Bolt⚡ という簡単に Slack 連携を実装するためのフレームワークを Cloud Functions for Firebase で動かす手順を紹介します。この記事時点で Bolt のバージョンは 1.2.0 です。

ちなみに 7/31-8/1 開催の [Google Cloud Next ’19 in Tokyo](https://cloud.withgoogle.com/next/tokyo/) に Slack Japan もブース出展しております。この記事を 2019/8/1 に読まれている方、私も会場におりますので、来場される方はぜひお立ち寄りください :pray: 

# tl;dr

* Bolt⚡ 日本語ドキュメント: https://slack.dev/bolt/ja-jp/tutorial/getting-started
* 完成形: https://github.com/seratch/bolt-on-cloud-functions-for-firebase
* こちらのスライドも参考にしてみてください: https://speakerdeck.com/roadagain/bolt-on-cloud-functions-for-firebase

# Bolt⚡ とは何か

Slack と連携するアプリケーションをつくるとき、以下の二つのやりとりがあります。何か便利な Slack 連携をつくるとき、この両方を行うアプリケーションを作ることが多いでしょう。

* 1) Slack に何か情報を送る（メッセージをポストする、ファイルをアップロードする、など）
* 2) Slack 内で発生した情報をもらう（Slack 内で発生したイベント、コマンド・ダイアログ・ボタンなどのユーザによる操作の内容を受け取る）

JavaScript/TypeScript の場合 1) は [slackapi/node-slack-sdk](https://github.com/slackapi/node-slack-sdk) を使って実装することができます。2) について、この記事では Slack が提供している [slackapi/bolt](https://github.com/slackapi/bolt) を紹介します。Bolt は Slack が今年 4 月末に公開したライブラリです。Bolt は 1) を簡単に行うためのユーティリティ（`say` や `respond`）も提供しています。

# Firebase プロジェクトを始める

この記事では Cloud Functions for Firebase についての説明は割愛します。

まず `firebase init` で functions のプロジェクトをつくって function の依存ライブラリに `@slack/bolt` を追加してください。

Bolt は 1.2.0 時点で Node 10.13.0 以上で動作します。 `package.json` の `engines.node` を以下のように書き換えてください。

```json
{
  "engines": {
    "node": "10"
  }
}
```

# Slack アプリの設定を行う

https://api.slack.com/apps にアクセスして Slack アプリをつくってください。動作確認したいワークスペースを指定して作成します。

次に Slash Commands のページにアクセスして `/echo-from-firebase` というコマンドをつくります。Request URL はこの時点ではダミーでもよいのでとりあえず保存します（その場合は初回デプロイしたあとで更新してください）。

<img height="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19163/3d6fdc33-f389-5383-93f5-036155750c54.png">

次に Bot Users のページにアクセスして、Bot ユーザを一つ作成してください。

最後に `Install App` からのこの Slack アプリをワークスペースにインストールしてください。これによって `Bot User OAuth Access Token` が発行されていますので、これを後ほど使用します。

# index.js の完成形

まず、完成形は以下のようになります。`/echo-from-firebase` というスラッシュコマンドを処理するコードがこれだけの行数で実装できます。以下で個々の内容を説明していきます。

```js
'use strict'
const functions = require('firebase-functions');
const config = functions.config();

const { App, ExpressReceiver } = require('@slack/bolt');
const expressReceiver = new ExpressReceiver({
    signingSecret: config.slack.signing_secret,
    endpoints: '/events',
    processBeforeResponse: true
});
const app = new App({
    receiver: expressReceiver,
    token: config.slack.bot_token,
    processBeforeResponse: true
});
app.error(console.log);

app.command('/echo-from-firebase', async ({ command, ack, say }) => {
    ack();
    say(`You said "${command.text}"`);
});

// https://{your domain}.cloudfunctions.net/slack/events
exports.slack = functions.https.onRequest(expressReceiver.app);
```

## firebase functions:config

config を以下のように設定して

```bash
firebase functions:config:set slack.signing_secret=522777abcabcabcabcabcabcabcabc
firebase functions:config:set slack.bot_token=xoxb-1234567890-123456789012-abcabcabcabcabcabc
```

それを以下のコードで呼び出します。このサンプルではリクエストの検証に必要な Signing Secret と API の利用に必要な Bot OAuth Access Token を設定します。

```js
const functions = require('firebase-functions');
const config = functions.config();
```

Signing Secret は Basic Information > App Credentials にあります。 Bot OAuth Access Token は Install App に Bot User OAuth Access Token として表示されています。


## Bolt の初期化

Bolt はデフォルトの実装は Express を使っています。今回はあとから `functions.https.onRequest` に渡す `Application` インスタンスを取得するために `ExpressReceiver` を定義して `App` の初期化を行なっています。

```js
const { App, ExpressReceiver } = require('@slack/bolt');
const expressReceiver = new ExpressReceiver({
    signingSecret: config.slack.signing_secret,
    endpoints: '/events',
    processBeforeResponse: true
});
const app = new App({
    receiver: expressReceiver,
    token: config.slack.bot_token,
    processBeforeResponse: true
});
app.error(console.log);
```

その必要がないケースでは、以下のようにさらにシンプルに `App` を初期化することもできます。

```js
const app = new App({
    token: process.env.SLACK_BOT_TOKEN,
    signingSecret: process.env.SLACK_SIGNING_SECRET,
    processBeforeResponse: true
});
```


## メインロジック

スラッシュコマンドを処理する関数を定義する場合は、以下のように `app.command` を使います。 

```js
app.command('/echo-from-firebase', async ({ command, ack, say }) => {
    ack();
    say(`You said "${command.text}"`);
});
```

`app.event` や `app.action` など他のリクエストを処理するためのメソッドももちろん定義されているので、ドキュメントを参照してみてください。

https://slack.dev/bolt/ja-jp/concepts

## Firebase 互換の関数を export する

最後に先ほど定義していた `expressReceicer` の `app` を `functions.https.onRequest` に渡して `slack` という名前で export します。これによって URL は `https://{your domain}.cloudfunctions.net/slack/events` のようになります。 URL の中の `events` は `endpoints: '/events'` の部分を変えれば違うパスにも変更できます。

```js
// https://{your domain}.cloudfunctions.net/slack/events
exports.slack = functions.https.onRequest(expressReceiver.app);
```

# 動作確認

最後にちゃんと動いているか、実際に確認してみましょう。

## Firebase にデプロイ

以下の通り、firebase-cli でデプロイします。作ったばかりのプロジェクトであれば Billing Info の更新をお忘れなく。

```bash
firebase deploy --only functions
```

## Slack 上でテスト

設定を行なった Slack ワークスペース内の動作確認を行いたいチャンネルにつくった Bot ユーザを invite してください。 `/invite @user-name` で招待するか、メンションしてください。

そして、 `/echo-from-firebase hello` と入力してみてください。 `You said "hello"` と応答してくれるはずです。


<img height="100" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19163/ddb413f1-7d6e-1525-27be-c2d68613376f.png">


# まとめ

簡単ですが Bolt を使った Slack 連携アプリを Cloud Functions for Firebase にデプロイする手順をご紹介しました。ぜひ試してみてください。

やってみて何かわからないことなどがあったら、以下のリンクからコミュニティに参加して、そこで質問をしてみてください！

>:wave: Slack コミュニティ公式グループ community.slack.com に参加しませんか？(日本語は #lang-japanese チャンネルへどうぞ！)
https://qiita.com/girlie_mac/items/93538f9a69eb4015f951#comment-a983ea977482e78f209c

また、繰り返しとなりますが　Slack Japan は 7/31 と 8/1 の [Google Cloud Next ’19 in Tokyo](https://cloud.withgoogle.com/next/tokyo/) にブース出展しております。この記事を 2019/8/1 に読まれた方でイベントに来場される方は、ぜひお立ち寄りください（私も Slack ブースにおります） :pray: