GAS 祝日なども考慮して、休日の前日にSlackに通知を送るスクリプトを書いてみた

## 概要

日本の祝日を考慮し、休みの日の前日にSlackでメセージを投げるGASのスクリプトを紹介する。

## 前提

下記の方法でSlack Appを使って通知用のWebhook URLが作成済みであるものとする。

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

## 方法

下記のようなファイル構成でそれぞれコードを記載した。

- main.gs
- config.gs
- memberinfo.js

それぞれの内容を記載する

### main.gs

```main.gs
function noticeWeekend() {
  const config = getConfig();
  const today = new Date();
  // 日本の祝日カレンダーのID（固定）
  const calendarId = "ja.japanese#holiday@group.v.calendar.google.com";
  const calendar = CalendarApp.getCalendarById(calendarId);

  if (!getIsNoticeDay(today, calendar, config)) {
    const message = "本日は通知対象日ではないため、通知は行いません。";
    console.log(message);
    return false;
  }

  if (!getIsLastBusinessDayBeforeWeekend(today, calendar, config)) {
    var message = "本日は週末前の最終営業日ではないため通知は行いません。";
    console.log(message);
    return false;
  }

  if (getIsLastBusinessDayBeforeMonthend(today, calendar, config)) {
    let message = "本日は週末前の最終営業日ですが、月末前の最終営業日でもあるため通知は行いません。。";
    console.log(message);
    return false;
  }

  const memberInfo = getMemberInfo();
  const slackUserIds = memberInfo.slackUserIds;

  var text = "";
  slackUserIds.forEach(function (slackUserId) {
    text += "<@" + slackUserId + "> ";
  });
  text +=
    "\n" +
    "今日は週末前の最終営業日です！\n日報システムに入力してから退勤お願い致します！";

  var payload = JSON.stringify({ text: text });

  sendSlackMessage(payload, config);
}

function getIsNoticeDay(today, calendar, config) {
  return !getIsHolidayOrWeekend(today, calendar, config);
}

function getIsLastBusinessDayBeforeWeekend(today, calendar, config) {
  const addDayNumber = 1;
  var addAfterDay = new Date(today);
  var addAfterDayIsHoliday = false;
  var isWeekendIncluded = false;
  do {
    addAfterDay.setDate(addAfterDay.getDate() + addDayNumber);
    const eventAddAfterDay = calendar.getEventsForDay(addAfterDay);

    var addAfterDayIsHoliday = eventAddAfterDay.length > 0;
    if (!isWeekendIncluded) {
      var isWeekendIncluded =
        addAfterDay.getDay() === config.dayOfWeeks.saturday.id ||
        addAfterDay.getDate() === config.dayOfWeeks.sunday.id;
    }
  } while (addAfterDayIsHoliday);

  const isWeekend =
    addAfterDay.getDay() === config.dayOfWeeks.saturday.id ||
    addAfterDay.getDate() === config.dayOfWeeks.sunday.id ||
    isWeekendIncluded;

  console.log("最終的判断日付: " + addAfterDay.toISOString());
  return isWeekend;
}

function getIsLastBusinessDayBeforeMonthend(today, calendar, config) {
  let endOfMonthDate = getEndOfMonthDate(today);
  const subDayNumber = 1;
  let finalBusinessDayBeforeMonthend;

  while (true) {
    if (!getIsHolidayOrWeekend(endOfMonthDate, calendar, config)) {
      finalBusinessDayBeforeMonthend = new Date(endOfMonthDate);
      break;
    }
    endOfMonthDate.setDate(endOfMonthDate.getDate() - subDayNumber);
  }

  // 年、月、日が完全に一致するかどうかを確認
  return getIsSameDate(today, finalBusinessDayBeforeMonthend)
}

function getEndOfMonthDate(today) {
  const todayYear = today.getFullYear();
  const todayMonth = today.getMonth() + 1; // getMonthは1月の場合０を、2月の場合1を返すため+1している。
  const lastDay = 0; // 月末日は日付を0に設定することで取得可能

  return new Date(todayYear, todayMonth, lastDay);
}

function getIsHolidayOrWeekend(day, calendar, config) {
  const isHoliday = calendar.getEventsForDay(day).length > 0;
  const isWeekend =
    day.getDay() === config.dayOfWeeks.saturday.id ||
    day.getDay() === config.dayOfWeeks.sunday.id;
  return isHoliday || isWeekend;
}

function getIsSameDate(dateOne, dateTwo) {
  return dateOne.getFullYear() === dateTwo.getFullYear() &&
    dateOne.getMonth() === dateTwo.getMonth() &&
    dateOne.getDate() === dateTwo.getDate();
}

function sendSlackMessage(payload, config) {
  try {
    const webhookUrl = config.webhookUrl;

    var options = {
      method: "post",
      contentType: "application/json",
      payload: payload,
    };

    UrlFetchApp.fetch(webhookUrl, options);
  } catch (e) {
    console.log(e);
    throw e;
  }
}
```

### config.gs

```config.gs
function getConfig() {
  return {
    webhookUrl: "実際のSlackのWebhookURLを設定",
    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

Slackにて本通知内容でメンションしたい人の詳細を開き、三点リーダをクリック、メンバーIDをコピーをクリック

```memberinfo.gs
function getMemberInfo() {
  return {
    slackUserIds: [
      "U0XXXXXXXXX", // Aさん
      "U0XXXXXXXXX", // Bさん
      "U0XXXXXXXXX", // Cさん
      "U0XXXXXXXXX"  // Dさん
    ],
  };
}
```

あとはGASの方のトリガー設定で毎日だいたい決まった時間に本スクリプトが実行されるようにすれば完了