Skip to content

Instantly share code, notes, and snippets.

@nekonenene
Last active May 3, 2024 06:37
Show Gist options
  • Save nekonenene/711b20112201a980abf1e29942fc3fb6 to your computer and use it in GitHub Desktop.
Save nekonenene/711b20112201a980abf1e29942fc3fb6 to your computer and use it in GitHub Desktop.
自身のGoogleカレンダーの予定すべてに対して特定のメアドを招待する Google Apps Script
const GUEST_EMAILS = [ // 招待するゲストのメールアドレス
'xxx@example.com',
];
const FETCHING_DAYS = 30; // 何日後までの予定を取得するか
function main() {
const dateFrom = new Date();
const dateTo = new Date(dateFrom.getTime() + (FETCHING_DAYS * 24 * 60 * 60 * 1000));
const calendar = Calendar.Calendars.get('primary');
Logger.log(calendar);
const events = getCalendarEvents(calendar.id, dateFrom, dateTo);
Logger.log(`Fetched ${events.length} events`);
GUEST_EMAILS.forEach(guestEmail => {
events.forEach(event => {
const invitedEvent = inviteGuestToEvent(calendar.id, event, guestEmail);
syncAttendance(calendar.id, invitedEvent, guestEmail);
});
});
}
/**
* 指定期間のカレンダーイベントすべてを取得する
*
* @param {string} calendarId - カレンダーID(≒メールアドレス)
* @param {Date} fromTime - 取得期間の開始日時
* @param {Date} toTime - 取得期間の終了日時
*
* @returns {Array<Event>} - See the [Events: list](https://developers.google.com/calendar/api/v3/reference/events/list?hl=en) documentation
*/
function getCalendarEvents(calendarId, fromTime, toTime) {
let pageToken = null;
let events = [];
do {
const response = Calendar.Events.list(calendarId, {
timeMin: fromTime.toISOString(),
timeMax: toTime.toISOString(),
singleEvents: true, // 繰り返しイベントを1つずつのイベントとして取得する
eventTypes: 'default', // 「不在」や「勤務場所」「サイレントモード」の予定は取得しない
maxAttendees: 100, // 参加者の最大取得数
orderBy: 'startTime',
pageToken: pageToken,
});
events = events.concat(response.items);
if (response.nextPageToken) {
pageToken = response.nextPageToken;
} else {
break;
}
} while (true);
return events;
}
/**
* カレンダーの特定イベントに指定したゲストがまだいないときに招待する
*
* @param {string} calendarId - 招待先となるカレンダーID(≒メールアドレス)
* @param {Event} event - 招待するイベント
* @param {string} guestEmail - 招待するゲストのメールアドレス
*
* @returns {Event} - 招待後のイベント(招待がおこなわれなかった場合は引数の event がそのまま返る)
*/
function inviteGuestToEvent(calendarId, event, guestEmail) {
const attendees = event.attendees || [];
const guestEmailsInEvent = attendees.map(attendee => attendee.email);
// イベントの参加者一覧にゲストがいない場合は招待
// 参加者一覧にメーリングリストがある場合、実際は指定ゲストが参加者にいるのに以下の招待処理がおこなわれる問題がある
if (!guestEmailsInEvent.includes(guestEmail)) {
Logger.log(`Inviting ${guestEmail} to ${event.summary} (${event.start.dateTime || event.start.date})`);
const eventClone = Calendar.Events.get(calendarId, event.id);
if (eventClone.attendees === undefined) {
eventClone.attendees = [];
}
eventClone.attendees.push({ email: guestEmail, responseStatus: 'needsAction' });
try {
return Calendar.Events.update(eventClone, calendarId, event.id);
} catch (e) {
Logger.log(`Failed to update the event: ${e}`);
return event;
}
}
return event;
}
/**
* カレンダーのオーナーが、イベントのゲストとして出欠入力をしている場合に、指定したゲストの出欠情報を同期する
*
* @param {string} calendarId - 同期元のカレンダーID(≒メールアドレス)
* @param {Event} event - 出欠情報を同期するイベント
* @param {string} guestEmail - 同期先のゲストのメールアドレス
*
* @returns {Event} - 同期後のイベント(同期がおこなわれなかった場合は引数の event がそのまま返る)
*/
function syncAttendance(calendarId, event, guestEmail) {
// イベントにゲストがいない場合は同期しない
if (event.attendees === undefined) return event;
// 参加者一覧にメーリングリストがある場合、実際は指定ゲストが参加者にいるのに見つからない問題がある
const guestAttendee = event.attendees.find(attendee => attendee.email === guestEmail);
if (guestAttendee === undefined) return event;
// イベントの主催者で、同期先のゲストが出欠を未入力であるなら出席にする
if (event.organizer.email === calendarId && guestAttendee.responseStatus === 'needsAction') {
Logger.log(`${calendarId} is the organizer, so ${guestEmail} must attend ${event.summary} (${event.start.dateTime || event.start.date})`);
const eventClone = Calendar.Events.get(calendarId, event.id);
eventClone.attendees.find(attendee => attendee.email === guestEmail).responseStatus = 'accepted';
try {
return Calendar.Events.update(eventClone, calendarId, event.id);
} catch (e) {
Logger.log(`Failed to update the event: ${e}`);
return event;
}
}
const ownerAttendee = event.attendees.find(attendee => attendee.email === calendarId);
if (ownerAttendee === undefined) return event;
// すでに出欠情報が一致しているなら同期しない
if (ownerAttendee.responseStatus === guestAttendee.responseStatus) return event;
Logger.log(`Syncing attendance between ${calendarId} and ${guestEmail} for ${event.summary} (${event.start.dateTime || event.start.date})`);
const eventClone = Calendar.Events.get(calendarId, event.id);
if (guestAttendee.responseStatus === 'needsAction') {
eventClone.attendees.find(attendee => attendee.email === guestEmail).responseStatus = ownerAttendee.responseStatus.toString();
try {
// ゲスト側の出欠変更は、ゲスト側のカレンダー設定によっては必ず失敗する
return Calendar.Events.update(eventClone, calendarId, event.id);
} catch (e) {
Logger.log(`Failed to update the event: ${e}`);
return event;
}
} else if (ownerAttendee.responseStatus === 'needsAction') {
eventClone.attendees.find(attendee => attendee.email === calendarId).responseStatus = guestAttendee.responseStatus.toString();
try {
return Calendar.Events.update(eventClone, calendarId, event.id);
} catch (e) {
Logger.log(`Failed to update the event: ${e}`);
return event;
}
}
return event;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment