👻

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

2021/10/30に公開

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

1、FlutterでSlack APIにPOSTリクエストし、削除ボタン付きのメッセージを送信

2、削除ボタンをクリックし、Slackがそのイベント内容をPOSTリクエストでCloudFunctionsで生成したエンドポイントに対して送信

3、CloudFunctionsでリクエストを受け取りFirestoreのデータを削除しに行く

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

slack.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
24.png

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

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

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

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

IMG_7947.jpg

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

スクリーンショット 2021-10-30 10.29.38.png

CloudFunctionsでFirestoreのデータを削除

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

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ではなくapplication/x-www-form-urlencodedでした。エンコードされたurlでデータが受送信されます。(ex. a=1&b=1 → %20a%3D1%26b%3D1)なのでリクエストを受け取った時にデコードする必要があります。

Node.jsの場合

const payload = JSON.parse(req.body.payload);

私は主にPythonに書いているので実験用で今回はAWS Lambda、CloudFunctionsでも試しました。

AWS Lambdaの場合

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の場合

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

Discussion