Google Apps Scriptでは「特定のカレンダーの更新」をトリガーとしてスクリプトを動かせるため、例えば「Aのカレンダーが更新された時、その更新内容をSlackに通知」というテクニックが作例で挙げられます。
そのケースではCalender APIのEvents:ListのAPIを利用して、「前回スクリプト実行時よりカレンダーが更新された差分を取得」するのだが、この時に時間や内容を変更した場合、「変更前はどのようなデータだったのか」という情報が得られないので、カレンダーのイベントのユニークIDに対してデータを管理する中間データをSpreadSheetやDBで保持する必要があるなど、愚直に実装しようとすると手がかかる。そのため、「定期的に直近2週間の予定を取得し、その結果を反映する」動きであれば擬似的に自分のサブカレンダーのデータをメインカレンダーに作ることができる。
以下作例。
const subCalendarId = "myCalender@example.com"; //同期させたいカレンダーIDを入れる
const mainCalendarId = 'myCalender2@example.com';
function syncSubCalendar() {
var timeMin = new Date().toISOString();
var timeMax = new Date(new Date().getTime() + 86400 * 1000 * 14).toISOString();// 2週間後の今
var options = {
singleEvents: true,
timeMin: timeMin,
timeMax: timeMax
}
var subEventList = Calendar.Events.list(subCalendarId, options);
var mainEventList = Calendar.Events.list(mainCalendarId, options);
// Sub Eventのidをリスト化
var subEventSummaries = {};
for (var i=0; i < subEventList.items.length; i++ ) {
subEventSummaries['private: [' + subEventList.items[i].id + ']'] = subEventList.items[i];
}
// Main Eventsに存在する「private: [...]」のイベントをリスト化して、subEventSummariesと比較
for (var i=0; i < mainEventList.items.length; i++) {
if(!mainEventList.items[i].organizer.self || mainEventList.items[i].summary.indexOf('private: [') < 0 ||
Object.keys(mainEventList.items[i].start).indexOf('dateTime') < 0 ||
Object.keys(mainEventList.items[i].end).indexOf('dateTime') < 0) {
continue;
}
// private eventの場合
if (Object.keys(subEventSummaries).indexOf(mainEventList.items[i].summary) > 0) {
var subEvent = subEventSummaries[mainEventList.items[i].summary];
// 開始・終了時間が一致しないならばアップデート
if ( mainEventList.items[i].start.dateTime!== subEvent.start.dateTime ||
mainEventList.items[i].end.dateTime!== subEvent.end.dateTime){
console.log('時間が違うのでアップデート');
Calendar.Events.update(
{
start: subEvent.start,
end: subEvent.end,
summary: mainEventList.items[i].summary
},
mainCalendarId,
mainEventList.items[i].id,
{},
{'If-Match': mainEventList.items[i].etag}
);
}
// subEventSummariesから値をdelete
delete subEventSummaries[mainEventList.items[i].summary];
} else {
// 同じidのイベントが存在しないので、削除として判定
console.log('delete event: ' + mainEventList.items[i].summary);
Calendar.Events.remove(mainCalendarId, mainEventList.items[i].id);
}
}
// ここまでに残ったsubEventSummariesは、新規追加分のイベントとして判定
for (var i=0; i<Object.keys(subEventSummaries).length;i++) {
var event = subEventSummaries[Object.keys(subEventSummaries)[i]];
if (Object.keys(event.start).indexOf('dateTime') < 0 ||
Object.keys(event.end).indexOf('dateTime') < 0) {
// 1日中やるイベントは追加しない
continue;
}
console.log('add event: ' + event.id);
Calendar.Events.insert({
end: event.end,
start: event.start,
summary: 'private: [' + event.id + ']'
}, mainCalendarId);
}
}
カレンダーイベントの同一性を示すプロパティは id
なので、それを利用してAPIコールの回数を必要最小限にしつつ同期を取るような実装にしている。変数名はサブカレンダーの内容をメインカレンダーに移すようなイメージ。
メインカレンダーの所有者のロールでGoogle Apps Scriptを作成して、30分おきの定期実行で仕込んでおくと良い感じにプライベートの予定を仕事相手に見せておく事ができます。
都合、summary(イベントのタイトル)にidを書いてしまったのですがdescriptionもEventList APIから取得できるので、説明欄にidを書いて同期しておいて、summaryは単純に[予定あり]とかにしちゃうのが外部への見え方としては綺麗かも。
あとは物理的なカレンダー統合の手段として複数のカレンダーをキュレーションするためのスクリプトとしても生きるかも。そういう時はちゃんとCalender APIを呼ぶ時にtry-catchで囲ってあげてください(?)
エンジニアとして働く90年生まれ。Web系技術を追っかけたり、PCガジェットや自転車いじりが趣味。オーディオオタク。