note見出し画像_1280_670_

Googleカレンダーの予定を一括出力・更新するGAS

本エントリは、「情シスSlack Advent Calendar 2019#2」の14日目に登録されています。

今年もアドベントカレンダーの季節がやってきました。
カレンダーと言えば、みなさん大好きなカレンダーがもうひとつありますよね。

そう、Googleカレンダー…!

という訳で今回はGoogleカレンダーネタにしました。

Googleカレンダーの残念な仕様

Googleカレンダー、便利ですよね。
ただ、個人的に不満を感じている仕様がひとつあります。
それは、「"予定作成時のゲストの変更権限"がデフォルトでOFF」ということ。

予定の作成

そのため、大半のユーザーは予定作成時に「予定を変更する」にチェックを入れず、予定の主催者(作成者)しか予定を編集できないようになり、「予定の主催者が不在なので、会議室や時間の変更ができない!」といった事態が起きがちです。
ガルーンなど他のスケジューラでは、デフォルトで同席者が編集権限を持っていることが多いので、初めて使う方は戸惑うことが多いかもしれません。

実は個々のカレンダー設定で、デフォルトのゲスト権限の設定を変更できるので、「予定を変更する」にチェックを入れておけば、毎回予定作成時にチェックを入れなくて済むのですが、この設定をしてくれないユーザーもいるので、結局ゲストに変更権限が無い予定が大量に生まれていきます。
※組織の設定でこちらを強制的にONにできればいいんですが、現状ではできないとのサポートの回答でした。

カレンダーのデフォルト設定

ゲストの編集権限が無い予定が増えてくると、
「休職に入る前に、特定の予定だけ主催者を変更しておきたい」
「主催者が急に行方不明になって…」
といったように、大量の予定を一括で変更したいケースも出てきます。
管理コンソールで全予定の一括譲渡も可能ですが、部分的な譲渡はできないので、管理者が手でポチポチ変更していく必要があり、地味に対応が大変です。

そこで、今回は以下の2つのGASを作成してみました。

1.対象のカレンダーの予定を一括出力するGAS
2.複数の予定のプロパティを一括変更するGAS

1.対象のカレンダーの予定を一括出力するGAS

それではまず、対象のカレンダーの予定を出力するGASを作成します。
以下の画像のようなスプレッドシートを作成し、「イベント一覧取得」というシート名をつけておいてください。

予定出力スプレッドシート_テンプレ

続いて、「ツール」→「スクリプトエディタ」を開き、以下のコードを貼り付けてください。
※イベント更新用の関数は後ほど追記します

var ss = SpreadsheetApp.getActiveSpreadsheet();
var listEventSheet = ss.getSheetByName("イベント一覧取得");
var updateEventSheet = ss.getSheetByName("イベント更新");

//実行メニューを作成
function onOpen() {
 var ui = SpreadsheetApp.getUi();
 var menu = ui.createMenu("GAS実行");
 menu.addItem("イベント取得", "listEvent");
 menu.addItem("イベント更新", "modifyEvent");
 menu.addToUi();
}

function listEvent(){
 var lastColum = listEventSheet.getLastColumn();
 var lastRow = listEventSheet.getLastRow();
 var startRow = 4;
 var dataColumnCount = 9;
 var dataCount = 0;

 //イベントを取得する対象のカレンダーIDを取得
 var strCalendarID = listEventSheet.getRange(1,2).getValue();
 
 //シートのリスト出力部をクリア
 listEventSheet.getRange(startRow, 1, listEventSheet.getLastRow(), dataColumnCount).clear();
 
 //予定の一覧(将来の分のみ)を取得
 //停止ユーザーのカレンダーを指定してもエラーになるので注意
 var eventList = Calendar.Events.list(strCalendarID, {
   timeMin: new Date().toISOString(),
   singleEvents: false,
   //orderBy: "startTime", //"orderBy"は"singleEvents"がtrueでないと使用できない
   maxResults: 1000 //APIの上限は2500
 });
 
 var values = [];
 if(eventList){
   for(var i = 0; i < eventList.items.length; i++){
     
     //まれに予定ではないレコードが含まれるので、"htmlLink"が存在しないレコードはスキップする
     if(!eventList.items[i].htmlLink)
     {
       continue;
     }
     
     var value = [];
     var strDate = ''
     var strStartTime = ''
     
     //終日予定の場合は"event.start.date"を、通常の予定の場合は"event.start.dateTime"を保持している
     if(eventList.items[i].start.dateTime){
       var startDate = new Date(eventList.items[i].start.dateTime);
       //var startDate = eventList.items[i].start.dateTime;
       
       //getMonthは0~11を返すので1を加算
       var strMonth = startDate.getMonth() + 1;
       
       
       //"時"の頭0が省略されるので付与
       var strHour = startDate.getHours();
       if(strHour < 10){
         strHour = '0' + strHour;
       }
       
       //"分"の頭0が省略されるので付与
       var strMinute = startDate.getMinutes();
       if(strMinute < 10){
         strMinute = '0' + strMinute;
       }
       
       strDate = startDate.getFullYear() + '-' + strMonth + '-' + startDate.getDate();
       strStartTime = strHour + ':' + strMinute;
     }
     else
     {
       strDate = eventList.items[i].start.date;
     }
     
     value.push(strDate);
     value.push(strStartTime);
     value.push(eventList.items[i].summary);
     value.push(eventList.items[i].organizer.email);
     value.push(eventList.items[i].visibility);
     value.push(eventList.items[i].guestsCanModify);
     value.push(eventList.items[i].recurrence);
     value.push(eventList.items[i].recurringEventId);
     value.push(eventList.items[i].id);

     values.push(value);
     dataCount++;
   }
   
   values.sort();
   
   //取得したデータをスプレッドシートにセット
   listEventSheet.getRange(startRow, 1, dataCount, dataColumnCount).setValues(values);
 }
}

そのままスクリプトエディタの「リソース」→「Googleの拡張サービス」を開き、「Calendar API」をONにして保存します。

カレンダーAPIをON

スクリプトを保存し、スプレッドシートをいったん閉じてから再度開いてください。
「カレンダーID」に対象者のカレンダーID(一般ユーザーの場合はメールアドレスと同一)をセットし、「GAS実行」→「イベント取得」を実行すると、対象ユーザーの予定の一覧がスプレッドシートに出力されます。
初回実行時は権限リクエスト画面が表示されますので、実行するアカウントを選択して許可してください。

なお、スクリプトを実行するには「カレンダーの管理」のG Suite権限が必要になりますので、適切な権限を持つユーザーで実行してください。

予定出力スプレッドシート_実行前

予定出力スプレッドシート

ずらずらーっと予定の一覧が出力されるはずです。

細かい解説はコード内のコメントで記載していますが、基本的に「Google Calendar API」の"Events: list"を呼び出しているだけです。
ポイントは"singleEvents: false"を指定すること。
これを指定しないと、繰り返し予定がすべて独立した予定として出力されてしまい、APIでの取得数の上限である2500レコードを埋め尽くしてしまいます。(Googleカレンダーの繰り返し予定は終了日が必須ではないので、エンドレスに続きます)
ただし"singleEvents: false"を指定するとなぜか"orderBy"が使用できなくなるという謎の仕様なので、ソートはJS側で実装しています。

サンプルのスプレッドシートの列にある情報以外にも取得できるプロパティは沢山ありますので、以下のリファレンスを参考にカスタマイズしてみてください。

繰り返し予定を出力した場合、「繰り返し条件(recurrence)」に"RRULE~"という文字列が入ってきます。これはRFC5545で定義されているiCalendarの繰り返し条件(Recurrence)の表記です。
詳細な説明は省きますが、"RRULE~"という文字列がセットされている予定は「繰り返し予定の大元の予定」だと認識しておけば大丈夫です。
逆に「繰り返し予定ID(recurringEventId)」に予定IDがセットされている場合は、「繰り返し予定の中から編集して単独の予定として出力された予定」のレコードとなります。

なお、カレンダー管理権限を持つユーザーで実行すると、対象のユーザーの非公開予定も出力されてしまうので、情報の取り扱いには注意しましょう。

2.複数の予定のプロパティを一括変更するGAS

続いて、複数の予定のプロパティを一括変更するGASを書き足します。
今回は「ゲストの予定編集権限(guestsCanModify)をONにする」というサンプルコードにしました。
もちろん他のプロパティを変更することも可能なので、お好みにカスタマイズしちゃってください。(特権管理者であれば、主催者を強制的に変更するなんてこともできちゃいます)

先ほど作成したスプレッドシートに、「イベント更新」という名前の新しいシートを追加し、以下のような列を用意します。

カレンダー一括変更

「対象カレンダーID」は予定取得の時と同じように、対象者のカレンダーID(一般ユーザーの場合はメールアドレスと同一)を、「対象イベントID」には更新対象のイベントIDをセットします。
イベントIDは先述の予定取得GASで取得した値をそのままコピペするのがいいでしょう。
「実行結果」は空欄のままにしておいてください。

スクリプトエディタを開き、以下の関数を追記して保存してください。

function modifyEvent(){
 var lastColum = updateEventSheet.getLastColumn();
 var lastRow = updateEventSheet.getLastRow();
 var startRow = 2;
 var numRows = lastRow - 1;
 var dataRange = updateEventSheet.getRange(startRow, 1, numRows, lastColum);
 var data = dataRange.getValues();
 
   for (var i = 0; i < data.length; ++i) {
   var row = data[i];
   row.rowNumber = i + startRow;
   
   //Result列がブランクであれば処理を実行    
   if (!row[2]) { 
     var result = "";
     
     var strCalendarID = row[0];
     var strEventID = row[1];
     
     try
     {
       //対象のイベントを取得
       var event = Calendar.Events.get(strCalendarID,strEventID);

       //「ゲストの変更を許可」をON
       event.guestsCanModify = true;
       
       //イベントを更新
       Calendar.Events.patch(event, strCalendarID, strEventID);

       result = "Success";
     }catch(e){
       result = "Error:" + e;
     }
     
     //実行結果をResult列にセット
     updateEventSheet.getRange(row.rowNumber, 3).setValue(result); 

   }
 }
}

スプレッドシートに戻り、「GAS実行」→「イベント更新」を実行すると、対象の予定が上から順に更新されていきます。

今回はCalendar APIの"Events: patch"を使用して、対象のイベントのプロパティを更新しています。
"Events: update"も用意されていますが、patchの方が必要最小限の更新で済むらしいです。実際に違いを試した訳ではないですが…

なお、繰り返し予定を対象にした場合、大元の予定を変更するとそれを元にしているすべての繰り返し予定のプロパティが変更されます
繰り返し予定のうちの特定のイベントのみを変更したい場合は、あらかじめ繰り返し予定の中の予定を編集し、別レコードとして出力された予定を対象にする必要があります。

おわりに

今回のサンプルGASをカスタマイズすれば、Googleカレンダーのイベントの様々なプロパティを一括で変更できます。
イベントの構造を理解するのが少し大変ですが、リファレンスに説明もありますので、お好みのGASにカスタマイズして快適なGoogleカレンダーライフを送ってください。


明日は引き続き「情シスSlack Advent Calendar 2019#1」の15日目で2枚抜きにチャレンジです。

この記事が気に入ったらサポートをしてみませんか?