GAS 祝日なども考慮して、月末営業日にSlackに通知を送るスクリプトを書いてみた

## 概要

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

## 前提

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

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

## 情報

下記の方法をちょこっと変えて月末営業日にSlackを送れるように修正した。

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

## 方法

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

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

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

### main.gs

```main.gs
function noticeMonthend() {
  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 (!getIsLastBusinessDayBeforeMonthend(today, calendar, config)) {
    let message = "本日は月末前の最終営業日ではないため通知は行いません。";
    console.log(message);
    return false;
  }

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

  let text = "";
  slackUserIds.forEach(function (slackUserId) {
    text += "<@" + slackUserId + "> ";
  });
  text +=
    "\n" +
    "本日は月末前の最終営業日です！\n日報の入力・確定を行ってから退勤をお願い致します！\n経費書類の提出もお忘れなく！";

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

  sendSlackMessage(payload, config);
}

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

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 getIsLastBusinessDayBeforeMonthend(today, calendar, config) {
  let endOfMonthDate = getEndOfMonthDate(today);
  const subDayNumber = 1;
  let finalBusinessDay;

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

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

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

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

```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の方のトリガー設定で毎日だいたい決まった時間に本スクリプトが実行されるようにすれば完了