株式会社スタジオ・アルカナでは一緒に働く仲間を募集しています
概要
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()