mofmofでは毎日朝会をやっていて、発言の順番は全メンバーをシャッフルしたものをGASでSlack通知してます。これの課題が「全メンバー」って部分で、稼働日でないメンバーや子供の送迎や通院などで朝会欠席するメンバーも含まれてるんですよね。

なので「この人とこの人いる?あ、いない?じゃあ飛ばして…ああ、自分の番か」という大変な認知負荷が朝っぱらからかかることになっています。つらい。

つくったもの

  1. ZoomのWebhookで、Zoomに人がくるたびFirebaseのFunctionsを呼ぶ
  2. 来た人をFirestoreに突っ込んでおく
  3. スケジュール設定しておいたFunctionsからSlack通知する

といった具合です。現状の致命傷は、スケジュール通りに起動してはいるものの通知が遅いってことですね。 朝会は9:45-10:00なんですが、通知が来るのが9:50なんですよね。遅いでしょ…。あとサボったのでシャッフルしてないです。

それはさておき、ZoomのWebhookやFunctionsの紹介をしていきます。

Firebase

Zoomの設定をするためにWebhookの通知先が必要なので、先にFirebase側の作業をしておきます。

一般的なセットアップの手順は省略します。FunctionsとFirestoreが使えるようにしてください。Functions使うためには従量課金プランにしておかないといけないので、その点だけご注意ください。

パッケージとして dayjsnode-fetch を使うので入れておきます。functionsディレクトリで以下のコマンドを実行してください。

npm install dayjs
npm install node-fetch@2

そしたらindex.jsを編集しましょう。

    const functions = require("firebase-functions");
    const admin = require("firebase-admin");

    const dayjs = require("dayjs");
    const fetch = require("node-fetch");

    admin.initializeApp();
    const db = admin.firestore();

    exports.add = functions.https.onRequest((req, res) => {
      const headers = req.headers;

      if (headers.authorization !== "<あとで埋めます>") {
        return res.status(403).send("Invalid");
      }

      const participatedUserName = req.body.payload.object.participant.user_name;
      functions.logger.info("userName", participatedUserName);

      const targetDate = dayjs().format("YYYY-MM-DD");

      db.collection("dates").doc(targetDate).get().then((documentSnapshot) => {
        if (documentSnapshot.exists) {
          const participated = documentSnapshot.data().users;
          documentSnapshot.ref.set({
            users: [...participated, participatedUserName],
          });
        } else {
          db.collection("dates").doc(targetDate).set({
            users: [participatedUserName],
          });
        }
      });

      res.send("completed!");
    });

    exports.notify = functions.pubsub
      .schedule("every day 9:47")
      .timeZone("Asia/Tokyo")
      .onRun((context) => {
        console.log("running");

        const targetDate = dayjs().format("YYYY-MM-DD");

        db.collection("dates")
          .doc(targetDate)
          .get()
          .then((querySnapshot) => {
            const participants = [];
            querySnapshot.data().users.forEach((user) => {
              participants.push(user);
            });

            fetch("<あとで埋めます>", {
              headers: { "Content-type": "application/json" },
              body: JSON.stringify({ "text": participants }),
            }, function (err, res, body) {
              console.log(body);
            });
          });
        return null;
      });

一旦これでOKです。ZoomやSlackから発行される部分は<あとで埋めます>になってます。 addのほうはうまいこと参加者をFirestoreに突っ込み、notifyのほうはいい感じにSlack通知をしてくれます。

addのエンドポイントがほしいので、デプロイしておきましょう。

    firebase deploy --only functions

デプロイ完了したら https://ドメイン/~/add というURLが発行されるはずなので、それを控えておきます。

Zoom

Zoom App Marketplaceからログインします。 https://marketplace.zoom.us/

したらヘッダから Build App をクリックしましょう。

JPbKFFnJ

開いたページの、Webhook OnlyのCreateをクリックします。モーダルが出てくるので適当に名前を埋めてください。

X R5TOS8

このあたりは適宜埋めてください。

R-YxAfuE

Event Subscriptionsのスイッチをクリックしてアクティブにしましょう。 したらEventsを追加します。 Add Events をクリックするとモーダルが表示されるので、Meeting > Participant/Host joined meeting にチェックを入れます。

mOuvJzu8

ABCbu3Ki

先ほどfunctionsをデプロイしたときに発行されたURLをここに入れましょう。ここまでの作業が終わったらこんな画面になっているはずです。できたらSaveしてください。

ZsjdxgoU

するとValidation Tokenが発行されるはずです。

qXdNRPGo

これをコピーして、addの冒頭に入れてあげましょう。

    exports.add = functions.https.onRequest((req, res) => {
      const headers = req.headers;

      if (headers.authorization !== "<あとで埋める>") { // ここです
        return res.status(403).send("Invalid");
      }

以上で完了です。再度デプロイしておきましょう。

このWebhookは現在ログイン中のZoomアカウントで有効になっているので、自分でZoomを立ち上げてmeetingに入ってみましょう。Firestoreのdates > YYYY-MM-DD > users に自分の名前が入るはずです。

Slack

https://slack.com/intl/ja-jp/help/articles/115005265063-Slack-での-Incoming-Webhook-の利用

このあたりを参考に、Webhookを設定してください。発行されたURLをfunctions > notify > あとで埋めます に設定すれば完了です。

    fetch("https://hooks.slack.com/services/~~~", {

これで時間になったらFirestoreを読んでSlack通知してくれるFunctionsが完成しました。

以上です!