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()

If this story triggered your interest, why don't you come and visit us?
新宿オフィスで一緒に働くエンジニアさん大募集!
株式会社スタジオ・アルカナ's job postings
3 Likes
3 Likes

Weekly ranking

Show other rankings
Like 大川 峻's Story
Let 大川 峻's company know you're interested in their content