概要
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
方法
- 下記のようにそれぞれのファイルでコードを記載する。
- 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",
],
};
}
- main.gs
- 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()