【Firebase】Cloud Functionsのトリガー関数でSlackの通知を送信する

## 開発環境
* Node: v10
* DB: Cloud Firestore
* 言語: TypeScript

## やりたいこと
FirebaseのCloud Functionsのトリガー関数が呼ばれた際に、Slackで追加されたことを通知したい。

## 環境構築

### Slackアプリケーションを作成する

[こちら](https://api.slack.com/apps)からSlackアプリケーションを作成します。

ページの**Create New App**ボタンを押下します。

![スクリーンショット_2021-11-04_19_39_02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263659/13477662-8753-7400-4745-8bfa7426a93c.png)


**App Name**にアプリケーションの名前を、**Pick a workspace to develop your app in:**に通知したいSlackのワークスペースを選択して、**Create App**ボタンを押してアプリケーションを作成してください。

![スクリーンショット 2021-11-04 19.40.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263659/6760e5da-ca8c-1cc7-d852-6329ef9fdc33.png)

### Webhook URLを作成する

画面左上にある、どのアプリケーションを選択しているかの項目に、作成したアプリの名前が表示されていることを確認してください。
Slackアプリケーションが作成できたら左のメニューから**Incoming Webhooks**をクリックします。

![スクリーンショット_2021-11-05_17_43_29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263659/7a58fe8c-a04e-aba7-392a-9a768538cb9e.png)

Incoming Webhooksの設定ページが開かれるので、**Activate Incoming Webhooks**のスイッチを有効にしてください。

有効にすると、**Webhook URLs for Your Workspace**のページが開かれるので、下部にある**Add New Webhook to Workspace**のボタンを押下してください。

![スクリーンショット_2021-11-04_19_47_47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263659/b34415da-1bfe-305a-fe26-a48f2f7a7ad0.png)

そうすると、Slackの通知先のチャンネルを選択する画面が開かれます。
任意のチャンネルを選択し、許可するボタンを押下してください。

![スクリーンショット_2021-11-04_16_51_33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263659/c6f2e381-e8d0-642a-b382-c8de2f2de9ed.png)

許可するとWebhook URLが作成されます。
こちらのURLにPOSTリクエストをすることで、選択したチャンネルに通知が飛ぶようになります。

試しに**Sample curl request to post to a channel:**のCopyボタンでURLをコピーして、ターミナルで実行してみましょう。

![スクリーンショット_2021-11-04_19_51_55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263659/311a2450-8110-b752-50d3-335b974e784a.png)

コマンドを実行すると**OK**と表示され、

![スクリーンショット_2021-11-04_19_55_50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263659/e3db9aee-e9fc-8922-9587-730893c89395.png)

Slackにも通知されていることが確認できます。

![スクリーンショット_2021-11-04_19_57_14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263659/10cc90e2-a2c3-bb67-85ed-3db2d01ee919.png)

## 実装
それでは本題のCloud Firestoreのあるコレクションにドキュメントが追加された際に、Slackで通知を飛ばすように実装していきます。

### パッケージインストール

[SlackのAPIクライアント](https://www.npmjs.com/package/@slack/webhook)をパッケージに追加します。

```
npm i @slack/webhook
```

### Cloud Functionsの環境変数にSlackのWebhookのURLを設定する
WebhookのURLは外に漏れると外部の人間から通知されるようになってしまいますので、Gitで管理してはいけません。
ということでCloud Functionsの環境変数からアクセスできるようにします。
下記のコマンドでWebhookのURLを環境変数に設定します。

```
// 環境変数設定のコマンド
firebase functions:config:set slack.web_hook_url={先ほど作成したWebhookのURL}
```

設定されているか確認するためには下記のコマンドを実行します。

```
// 環境変数確認のコマンド
firebase functions:config:get 

// 出力結果
{
  "slack": {
    "web_hook_url": "https://hooks.slack.com/services/XXX/YYY/ZZZ"
  }
}
```

エミュレーターでも環境変数を使いたい場合は、[こちらの記事](https://qiita.com/yamotuki/items/7bc393f33c7c723806a2)が参考になります。

### Slackに通知を送る
Slackに通知を送る実装はこちらです。

``` slackService.ts
import * as slack from '@slack/webhook';

export class SlackService {
    /**
     * Slackの通知を送信する
     * @param webhookUrl WebhookのURL
     * @param message 通知する内容
     * @returns 通知送信のPromiss
     */
    static sendMessage = (webhookUrl: string, message: string): Promise<slack.IncomingWebhookResult> => {
        const webhook = new slack.IncomingWebhook(webhookUrl);
        return webhook.send({
            text: message,
        });
    };
}
```

引数のwebhookUrlのURLに、引数messageの内容の通知を送信します。

### 環境変数からWebhookのURLを取得する
環境変数からWebhookのURLを取得する実装はこちらです。

``` slackWebhookUrl.ts
import * as functions from 'firebase-functions';

export class SlackWebHookUrl {
    static webhookUrl = () => {
        return functions.config().slack.web_hook_url;
    }
}
```
functions.config()で環境変数にアクセスできます。
slack.web_hook_urlの部分は、先ほどコマンドで実行した際につけた名前です。
今後WebhookURLが追加された時にここに追加していく想定でクラスにしています。

### ドキュメントが追加された際にSlack送信処理を呼び出す
今回はpostsコレクションにドキュメントが追加された想定で実装します。
コレクション名やimport文などは適宜変更してください。

``` postFunctions.ts
import * as functions from 'firebase-functions';
import { Post } from '../entities/post';
import { SlackService } from '../services/slackService';
import { SlackWebHookUrl } from '../constants/slackWebHookUrl';

export const onCreatePost = functions.firestore.document(`posts/{postId}`).onCreate(async (snap, context) => {
    // 追加されたドキュメント
    const post = snap.data() as Post
    // メッセージデータ作成
    const messageData = {
        'ID': snap.id,
        '名前': post.name,
        'リクエスト時刻': post.createdAt.toDate().toLocaleString(),
    }
    // メッセージを見やすくする
    const message = JSON.stringify(messageData, null, '\t');
    // Slackに通知を送信する
    await SlackService.sendMessage(SlackWebHookUrl.webhookUrl(), message);
});

```

### 実行結果
実装できたらデプロイし、postsコレクションにドキュメントを追加し、Slackの通知が送信されることを確認します。

![スクリーンショット_2021-11-05_17_37_17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263659/cde91706-a08a-6ee8-4c8e-139b0d9db21e.png)

以上です。