Flutterの通報をSlackに通知し、Firestoreのデータを削除する


Flutter×Firebaseの構成のSNSモバイルアプリを開発中です。その中で通報機能を作成したのですが通報内容をボタン付きでSlackに送信して削除ボタンを押したら通報された投稿を削除しに行く一連の流れを構成し、通報対応を簡易かつプログラミングの知識なしで行うようにしました。使用する言語はDartとNode.jsです

#### １. FlutterでSlack APIにPOSTリクエストし、削除ボタン付きのメッセージを送信
#### ２. 削除ボタンをクリックし、Slackがそのイベント内容をPOSTリクエストでCloudFunctionsで生成したエンドポイントに対して送信
#### ３. CloudFunctionsでリクエストを受け取りFirestoreのデータを削除しに行く

今回のアーキテクチャは以下の通りです



![slack.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1295942/e3908253-f635-6dc2-bcf8-0a0226d6c71a.png)

・Slack APIについて以前も取り上げたのでこちら参考までに

https://qiita.com/ymktmk_tt/items/ef90884d94190f7ae951

## 　FlutterからSlackにボタン付きメッセージを送信

以下の画面にてSlackワークスペースと連携してURLを生成しましょう（これはのちに使います）
サンプルを見ればわかると思うのですがHTTP POSTリクエストで生成したURLに対してjsonタイプのデータを送信するとSlackにメッセージを送信することできます。

![スクリーンショット 2021-10-30 11.07.![slack.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1295942/b5086c13-9778-9864-63de-755c8f2bede0.png)
24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1295942/7fda8c88-2109-9bd1-c698-300eda609da4.png)


DartでHTTPリクエストを送ります。ライブラリはhttpを使います。


通報をSlackに通知するメソッド

```dart
static Future<void> slack(PostModel postData, String reasonValue) async {
    const String slackToken =
        '<取得したToken>';
    Uri slackUri = Uri.parse("https://slack.com/api/chat.postMessage");
    Map<String, Object> data = {
      "channel": "<送信したいチャンネルID>",
      "text": postData.title,
      "attachments": [
        {
          "fallback": "fallback string",
          "title": "このスレッドを削除しますか？",
          "callback_id": "callback_id value",
          "color": "#FF0000",
          "attachment_type": "default",
          "actions": [
            {
              "name": "削除",
              "text": "削除",
              "type": "button",
              "style": "danger",
              "value": postData.postId,
              "confirm": {
                "title": "本当に削除しますか？",
                "text": "このスレッドは削除されてしまいます",
                "ok_text": "Yes",
                "dismiss_text": "No"
              }
            }
          ]
        }
      ]
    };
    final http.Response response = await http.post(
      slackUri,
      headers: {
        "Content-Type": "application/json",
        "Authorization": "Bearer $slackToken",
      },
      body: json.encode(data),
    );
    print(response);
}
```

送信できるとこのような感じで表示されます。

![IMG_7946.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1295942/9d624d70-9bc3-2ded-5555-e8c36ab1f41d.jpeg)

## 　SlackボタンアクションイベントをCloudFunctionsに送信

![IMG_7947.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1295942/e2b06d81-dc3b-fdc7-0068-51b25dab33e8.jpeg)


Slack APIのInteractivityはクリックイベントの内容を設定したエンドポイントに対してHTTP POSTリクエストを送信します。ここでCloudFunctionsのHTTPトリガのURLを貼り付けます。

![スクリーンショット 2021-10-30 10.29.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1295942/67189919-fddc-5e08-677c-f05c1f17f5a7.png)


## 　CloudFunctionsでFirestoreのデータを削除

CloudFunctionsでSlackからのHTTP POSTリクエストをイベントを元にFirestoreのデータを削除します
今回はpostコレクションとそのサブコレクションも削除します。

```javascript
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp(functions.config().firebase);

// 通報されたスレッド削除
exports.deletePost = functions.region("asia-northeast1")
    .https.onRequest(async function(req, res) {
      const payload = JSON.parse(req.body.payload);
      // ドキュメントIDを受け取る
      const postId = payload["actions"][0]["value"];
      const deleteDocumentRecursively = async function(postId) {
        const collections = await postId.listCollections();
        if (collections.length > 0) {
          for (const collection of collections) {
            const snapshot = await collection.get();
            for (const doc of snapshot.docs) {
              await deleteDocumentRecursively(doc.ref);
            }
          }
        }
        await postId.delete();
      };
      await deleteDocumentRecursively(admin.firestore().collection("post").doc(postId));
      res.status(200).send("削除成功！");
    });

```

# 　補足説明

## 　application/x-www-form-urlencoded　について

SlackからのHTTP POSTリクエストのContent-typeはapplication/jsonではなく<strong>application/x-www-form-urlencoded</strong>でした。エンコードされたurlでデータが受送信されます。（ex. a=1&b=1　→ %20a%3D1%26b%3D1）なのでリクエストを受け取った時にデコードする必要があります。

## 　Node.jsの場合

```javascript
const payload = JSON.parse(req.body.payload);
```
私は主にPythonに書いているので実験用で今回はAWS Lambda、CloudFunctionsでも試しました。

### 　AWS Lambdaの場合
```python
import json
import urllib.request
import urllib.parse

def lambda_handler(event, context):

    data = event["body"]
    payload = urllib.parse.unquote(data)
    payload = payload.replace("payload=", "")
    params = json.loads(payload)

```
### 　CloudFunctionsの場合

```python
import json
import urllib.request
import urllib.parse

def hello_world(request):
    # get_data()はPOSTされたデータをそのまま受け取れる
    data = request.get_data()
    payload = urllib.parse.unquote(data)
    payload = payload.replace("payload=", "")
    params = json.loads(payload)

```

# 　参考文献

・slack APIについて

https://hacknote.jp/archives/47488/

https://yuelab82.hatenablog.com/entry/slack_button_api_2

https://qiita.com/flower81/items/3411ac50855d65202a9c

・cloud functionsについて

https://cloud.google.com/functions/docs/writing/http#writing_http_helloworld-nodejs

https://firebase.google.com/docs/functions/http-events?hl=ja

https://qiita.com/toshiaki_takase/items/ce65cd5582a80917b52f