1
/
5

GASを使ってGoogleカレンダーに特定の予定があったときに前営業日にSlackに通知を送る仕組みを作ってみる

概要

GASを使い、Googleカレンダーに特定の予定があった時にSlackに通知を送る仕組みを作ってみたので方法を簡単にまとめる。

注意

すべての予定をSlack通知したい場合、GASなんか使わずにSlackのGoogleカレンダーのアプリを使ったほうがはるかに楽

前提

  • 下記の内容を実施しSlack通知用のSlackアプリの作成が完了していること。

https://qiita.com/miriwo/items/cbb4b37b3f5abecc835f

  • GAS作成時に下記の内容を実施し、タイムゾーンが日本に設定されていること。

https://qiita.com/miriwo/items/cf21884c3f907df08e47

  • 下記の方法でチェック対象のGoogleカレンダーのカレンダーIDが確認できること。

https://qiita.com/miriwo/items/b00b3282cc428fc1c480

方法

  1. 下記のようにそれぞれのファイルでコードを記載する。
    • main.gs
      function noticeSpecificEvent() {
        const config = getConfig();
        const today = new Date();
      
        // 日本の祝日カレンダーのID(固定)
        const holidayCalendarId = "ja.japanese#holiday@group.v.calendar.google.com";
        const holidayCalendar = CalendarApp.getCalendarById(holidayCalendarId);
      
        const targetCalender = CalendarApp.getCalendarById(config.checkTargetCalendarId);
      
        if (!getIsNoticeDay(today, holidayCalendar, config)) {
          const message = "本日は通知対象日ではないため、通知は行いません。";
          console.log(message);
          return false;
        }
      
        if (!getIsLastBusinessDayBeforeSpecificEvent(today, holidayCalendar, targetCalender, config)) {
          let message = `本日は「${config.specificEventTitle}」の予定前の最終営業日ではないため通知は行いません。`;
          console.log(message);
          return false;
        }
      
        const memberInfo = getMemberInfo();
        const slackUserIds = memberInfo.slackUserIds;
      
        let text = "";
        slackUserIds.forEach(function (slackUserId) {
          text += "<@" + slackUserId + "> ";
        });
        text +=
          "\\\\n" +
          `本日は「${config.specificEventTitle}」の予定前の最終営業日です!\\\\n予定を忘れないようにしましょう!`;
      
        let payload = JSON.stringify({ text: text });
      
        sendSlackMessage(payload, config);
      }
      
      function getStartOfDay(date) {
          return new Date(date.getFullYear(), date.getMonth(), date.getDate());
      }
      
      /**
       * 通知対象日かどうかのboolを返却
       *
       * @param {Date} today
       * @param {Calendar} holidayCalendar
       * @param {Object} config
       * @returns {Boolean}
       */
      function getIsNoticeDay(today, holidayCalendar, config) {
        return !getIsHolidayOrWeekend(today, holidayCalendar, config);
      }
      
      /**
       * 指定された日付が祝日か休日かどうかのboolを返却
       *
       * @param {Date} day
       * @param {Calendar} holidayCalendar
       * @param {Object} config
       * @returns {Boolean}
       */
      function getIsHolidayOrWeekend(day, holidayCalendar, config) {
        const isHoliday = holidayCalendar.getEventsForDay(day).length > 0;
        const isWeekend =
          day.getDay() === config.dayOfWeeks.saturday.id ||
          day.getDay() === config.dayOfWeeks.sunday.id;
        return isHoliday || isWeekend;
      }
      
      /**
       * 指定予定前の最終営業日かどうかのboolを返却
       *
       * @param {Date} today
       * @param {Calendar} holidayCalendar
       * @param {Calendar} targetCalender
       * @param {Object} config
       * @returns {Boolean}
       */
      function getIsLastBusinessDayBeforeSpecificEvent(today, holidayCalendar, targetCalender, config) {
        let specificEventDates = getSpecificEventDates(today, targetCalender, config);
        let isLastBusinessDayBeforeSpecificEvent = false;
      
        if (specificEventDates.length === 0) {
          const message = "指定された予定が見つかりませんでした。";
          console.log(message);
          return isLastBusinessDayBeforeSpecificEvent;
        }
      
        const subDayNumber = 1;
        let lastBusinessDayBeforeSpecificEventDates = [];
      
        specificEventDates.forEach(function (specificEventDate) {
          while (true) {
            const specificEventBeforeDate = new Date(specificEventDate);
            // 指定された予定の前日を算出
            const specificEventBeforeDay = specificEventDate.getDate() - subDayNumber;
            specificEventBeforeDate.setDate(specificEventBeforeDay);
            if (!getIsHolidayOrWeekend(specificEventBeforeDate, holidayCalendar, config)) {
              lastBusinessDayBeforeSpecificEventDates.push(specificEventBeforeDate);
              break;
            }
            // falseだった場合、特定の予定の前日を特定の予定の日としてセットして本ループを再度実行
            specificEventDate = specificEventBeforeDate;
          }
        });
      
        // 年、月、日が完全に一致するかどうかを確認
        lastBusinessDayBeforeSpecificEventDates.forEach((lastBusinessDayBeforeSpecificEventDate) => {
          if (getIsSameDate(today, lastBusinessDayBeforeSpecificEventDate)) {
            isLastBusinessDayBeforeSpecificEvent = true;
          }
        });
        return isLastBusinessDayBeforeSpecificEvent;
      }
      
      /**
       * 2つの日付が完全に一致するかどうかのboolを返却
       *
       * @param {Date} dateOne
       * @param {Date} dateTwo
       * @returns {Boolean}
       */
      function getIsSameDate(dateOne, dateTwo) {
        return dateOne.getFullYear() === dateTwo.getFullYear() &&
          dateOne.getMonth() === dateTwo.getMonth() &&
          dateOne.getDate() === dateTwo.getDate();
      }
      
      /**
       * 指定された予定の日付を取得
       *
       * @param {Date} today
       * @param {Calendar} targetCalender
       * @param {Object} config
       * @returns {Array}
       */
      function getSpecificEventDates(today, targetCalender, config) {
          const oneMonthLater = getOneMonthLater(today);
          const targetCalenderEvents = targetCalender.getEvents(today, oneMonthLater);
      
          let specificEventDates = [];
          targetCalenderEvents.forEach(function (targetCalenderEvent) {
              if (targetCalenderEvent.getTitle() === config.specificEventTitle) {
                  specificEventDates.push(targetCalenderEvent.getStartTime());
              }
          });
          return specificEventDates;
      }
      
      /**
       * 指定された日付の1ヶ月後の日付を取得
       *
       * @param {Date} date
       * @returns {Date}
       */
      function getOneMonthLater(date) {
          const newDate = new Date(date);
          newDate.setMonth(newDate.getMonth() + 1);
          return newDate;
      }
      
      /**
       * Slackにメッセージを送信
       *
       * @param {String} payload
       * @param {Object} config
       */
      function sendSlackMessage(payload, config) {
        try {
          const webhookUrl = config.webhookUrl;
      
          let options = {
            method: "post",
            contentType: "application/json",
            payload: payload,
          };
      
          UrlFetchApp.fetch(webhookUrl, options);
        } catch (e) {
          console.log(e);
          throw e;
        }
      }
      
      function noticeSpecificEvent() {
        const config = getConfig();
        const today = new Date();
      
        // 日本の祝日カレンダーのID(固定)
        const holidayCalendarId = "ja.japanese#holiday@group.v.calendar.google.com";
        const holidayCalendar = CalendarApp.getCalendarById(holidayCalendarId);
      
        const targetCalender = CalendarApp.getCalendarById(config.checkTargetCalendarId);
      
        if (!getIsNoticeDay(today, holidayCalendar, config)) {
          const message = "本日は通知対象日ではないため、通知は行いません。";
          console.log(message);
          return false;
        }
      
        if (!getIsLastBusinessDayBeforeSpecificEvent(today, holidayCalendar, targetCalender, config)) {
          let message = `本日は「${config.specificEventTitle}」の予定前の最終営業日ではないため通知は行いません。`;
          console.log(message);
          return false;
        }
      
        const memberInfo = getMemberInfo();
        const slackUserIds = memberInfo.slackUserIds;
      
        let text = "";
        slackUserIds.forEach(function (slackUserId) {
          text += "<@" + slackUserId + "> ";
        });
        text +=
          "\\\\n" +
          `本日は「${config.specificEventTitle}」の予定前の最終営業日です!\\\\n予定を忘れないようにしましょう!`;
      
        let payload = JSON.stringify({ text: text });
      
        sendSlackMessage(payload, config);
      }
      
      function getStartOfDay(date) {
          return new Date(date.getFullYear(), date.getMonth(), date.getDate());
      }
      
      /**
       * 通知対象日かどうかのboolを返却
       *
       * @param {Date} today
       * @param {Calendar} holidayCalendar
       * @param {Object} config
       * @returns {Boolean}
       */
      function getIsNoticeDay(today, holidayCalendar, config) {
        return !getIsHolidayOrWeekend(today, holidayCalendar, config);
      }
      
      /**
       * 指定された日付が祝日か休日かどうかのboolを返却
       *
       * @param {Date} day
       * @param {Calendar} holidayCalendar
       * @param {Object} config
       * @returns {Boolean}
       */
      function getIsHolidayOrWeekend(day, holidayCalendar, config) {
        const isHoliday = holidayCalendar.getEventsForDay(day).length > 0;
        const isWeekend =
          day.getDay() === config.dayOfWeeks.saturday.id ||
          day.getDay() === config.dayOfWeeks.sunday.id;
        return isHoliday || isWeekend;
      }
      
      /**
       * 指定予定前の最終営業日かどうかのboolを返却
       *
       * @param {Date} today
       * @param {Calendar} holidayCalendar
       * @param {Calendar} targetCalender
       * @param {Object} config
       * @returns {Boolean}
       */
      function getIsLastBusinessDayBeforeSpecificEvent(today, holidayCalendar, targetCalender, config) {
        let specificEventDates = getSpecificEventDates(today, targetCalender, config);
        let isLastBusinessDayBeforeSpecificEvent = false;
      
        if (specificEventDates.length === 0) {
          const message = "指定された予定が見つかりませんでした。";
          console.log(message);
          return isLastBusinessDayBeforeSpecificEvent;
        }
      
        const subDayNumber = 1;
        let lastBusinessDayBeforeSpecificEventDates = [];
      
        specificEventDates.forEach(function (specificEventDate) {
          while (true) {
            const specificEventBeforeDate = new Date(specificEventDate);
            // 指定された予定の前日を算出
            const specificEventBeforeDay = specificEventDate.getDate() - subDayNumber;
            specificEventBeforeDate.setDate(specificEventBeforeDay);
            if (!getIsHolidayOrWeekend(specificEventBeforeDate, holidayCalendar, config)) {
              lastBusinessDayBeforeSpecificEventDates.push(specificEventBeforeDate);
              break;
            }
            // falseだった場合、特定の予定の前日を特定の予定の日としてセットして本ループを再度実行
            specificEventDate = specificEventBeforeDate;
          }
        });
      
        // 年、月、日が完全に一致するかどうかを確認
        lastBusinessDayBeforeSpecificEventDates.forEach((lastBusinessDayBeforeSpecificEventDate) => {
          if (getIsSameDate(today, lastBusinessDayBeforeSpecificEventDate)) {
            isLastBusinessDayBeforeSpecificEvent = true;
          }
        });
        return isLastBusinessDayBeforeSpecificEvent;
      }
      
      /**
       * 2つの日付が完全に一致するかどうかのboolを返却
       *
       * @param {Date} dateOne
       * @param {Date} dateTwo
       * @returns {Boolean}
       */
      function getIsSameDate(dateOne, dateTwo) {
        return dateOne.getFullYear() === dateTwo.getFullYear() &&
          dateOne.getMonth() === dateTwo.getMonth() &&
          dateOne.getDate() === dateTwo.getDate();
      }
      
      /**
       * 指定された予定の日付を取得
       *
       * @param {Date} today
       * @param {Calendar} targetCalender
       * @param {Object} config
       * @returns {Array}
       */
      function getSpecificEventDates(today, targetCalender, config) {
          const oneMonthLater = getOneMonthLater(today);
          const targetCalenderEvents = targetCalender.getEvents(today, oneMonthLater);
      
          let specificEventDates = [];
          targetCalenderEvents.forEach(function (targetCalenderEvent) {
              if (targetCalenderEvent.getTitle() === config.specificEventTitle) {
                  specificEventDates.push(targetCalenderEvent.getStartTime());
              }
          });
          return specificEventDates;
      }
      
      /**
       * 指定された日付の1ヶ月後の日付を取得
       *
       * @param {Date} date
       * @returns {Date}
       */
      function getOneMonthLater(date) {
          const newDate = new Date(date);
          newDate.setMonth(newDate.getMonth() + 1);
          return newDate;
      }
      
      /**
       * Slackにメッセージを送信
       *
       * @param {String} payload
       * @param {Object} config
       */
      function sendSlackMessage(payload, config) {
        try {
          const webhookUrl = config.webhookUrl;
      
          let options = {
            method: "post",
            contentType: "application/json",
            payload: payload,
          };
      
          UrlFetchApp.fetch(webhookUrl, options);
        } catch (e) {
          console.log(e);
          throw e;
        }
      }
      
      
    • config.gs
      function getConfig() {
        return {
          checkTargetCalendarId: "参照するGoogleカレンダーのカレンダー ID",
          specificEventTitle: "特定の予定のタイトル",
          webhookUrl:
            "用意したSlackアプリのWebhook URL",
          dayOfWeeks: {
            sunday: {
              name: "sunday",
              id: 0,
            },
            monday: {
              name: "monday",
              id: 1,
            },
            tuesday: {
              name: "tuesday",
              id: 2,
            },
            wednesday: {
              name: "wednesday",
              id: 3,
            },
            thursday: {
              name: "thursday",
              id: 4,
            },
            friday: {
              name: "friday",
              id: 5,
            },
            saturday: {
              name: "saturday",
              id: 6,
            },
          },
        };
      }
      
      function getConfig() {
        return {
          checkTargetCalendarId: "参照するGoogleカレンダーのカレンダー ID",
          specificEventTitle: "特定の予定のタイトル",
          webhookUrl:
            "用意したSlackアプリのWebhook URL",
          dayOfWeeks: {
            sunday: {
              name: "sunday",
              id: 0,
            },
            monday: {
              name: "monday",
              id: 1,
            },
            tuesday: {
              name: "tuesday",
              id: 2,
            },
            wednesday: {
              name: "wednesday",
              id: 3,
            },
            thursday: {
              name: "thursday",
              id: 4,
            },
            friday: {
              name: "friday",
              id: 5,
            },
            saturday: {
              name: "saturday",
              id: 6,
            },
          },
        };
      }
      
      
    • memberInfo.gs
      function getMemberInfo() {
        return {
          slackUserIds: [
            "メンションをつけたいユーザーのslackID",
          ],
        };
      }
      
      function getMemberInfo() {
        return {
          slackUserIds: [
            "メンションをつけたいユーザーのslackID",
          ],
        };
      }
      
      
  2. main.gsのnoticeSpecificEvent()を毎日1回通知を行いたい時間に実行する設定をする

これでおそらく休日・祝日も考慮し、特定の予定の前営業日に通知が行われるはずである。

参考文献

https://developers.google.com/apps-script/reference/calendar/calendar?hl=ja#getEvents(Date,Date)

https://developers.google.com/apps-script/reference/calendar/calendar-event?hl=ja

https://developers.google.com/apps-script/reference/calendar/calendar-event?hl=ja#getStartTime()

このストーリーが気になったら、遊びに来てみませんか?
新宿オフィスで一緒に働くエンジニアさん大募集!
株式会社スタジオ・アルカナでは一緒に働く仲間を募集しています
3 いいね!
3 いいね!

同じタグの記事

今週のランキング

大川 峻さんにいいねを伝えよう
大川 峻さんや会社があなたに興味を持つかも