Slackがここまで普及してきて、リモートワークも普及してくると、自身のStatusをこまめに変更して状況をお知らせするというシーンも増えてきますが、案外変更わすれて「保育園の送りが終わったのに対応出来ませんのママだった」「仕事が終わってたのにWork from Homeのままだった」みたいなことが起こるといやだなーということで、Googleカレンダーを読み込んで自動的にStatusを変えるGoogle Apps Scriptアプリを書いてみました。

初出: note

用意するもの

  • Google Spreadsheet(tokenを置いておくための場所)
  • 上記に紐付くスクリプト
  • Slackのtoken(必要な権限…users.profile:write) ※昔作ったlegacy tokenで開発しちゃってるので、適切な権限を持ったtokenの作り方は別途調べてください…
  • Googleカレンダーで新しい専用のカレンダーを作成する

これらを用意したら、以下のコード①~をスクリプトに並べた上で、setStatusTrigger関数を毎日未明に起動するようトリガーを掛ければ動作します

使い方

用意した専用のカレンダーに、ステータスを設定したい時間帯に予定をいれ、タイトルに表示する文字列を、説明に絵文字を設定するだけです。

画像1

最終的にこんな塩梅のカレンダーになります

画像2

コード① Googleカレンダーを読み込むルーチン

用意したGoogleカレンダーのカレンダーID(@group.calendar.google.comで終わるもの)を事前に確認ください。

// カレンダー取得
function getScheduleCalendar(startDate, endDate){
 var cal = CalendarApp.getCalendarById("*******@group.calendar.google.com");
 var myevents =  cal.getEvents(startDate, endDate);
 return myevents;
}

この中の***で書かれている部分に、用意したカレンダーのカレンダーIDを入れます。

コード② SlackのAPIを呼ぶルーチン

単純にSlackのAPIを呼んでいます。妙にAPIを呼ぶルーチンが重々しいのは、もっと頻繁にAPIを叩くスクリプトで使ったコードの使い回しです…

// Slack Status設定
function setSlackStatus(expire_time, free_status_text, free_status_emoji){
 var expire_unixtime = expire_time.getTime()/1000;
 // tokenを取得する
 var logsheet = SpreadsheetApp.openByUrl("シートのURL").getSheetByName('シート名');
 var token = logsheet.getRange('tokenがあるセル名').getValue();
 var json = JSON.stringify(
   {
     "status_text": free_status_text,
     "status_emoji": free_status_emoji,
     "status_expiration": expire_unixtime
   }
 );
 call_slack_api_common(token, 'users.profile.set',{"profile" : json});
};

/**
* call_slack_api_common(slack_token, api, param)
* @param {string} slack_token APIで利用するtoken
* @param {string} api 呼び出したいAPIの名称
* @param {Hash} param APIに受け渡すパラメータ
* @return {Hash} APIの返り値
*/
function call_slack_api_common(slack_token, api, param) {
 // GETメソッドでAPIを叩く専用になっていることに留意
 // URL生成1行コードは http://d.hatena.ne.jp/snaka72/20090220/1235141033 こちらから頂いた
 var apiUrl = "https://slack.com/api/"+api+"?"+[(enc=encodeURIComponent)(i)+"="+enc(param[i]) for (i in param)].join('&');
 var apiOptions =
 {
   "headers" : {
     Authorization: 'Bearer ' + slack_token
   },
   "muteHttpExceptions" : true,
   "method" : "get"
 };

 var responseApi = [];
 var responseArray = [];
 var responseOk = '';

 do {
   responseApi = UrlFetchApp.fetch(apiUrl, apiOptions );
   if( responseApi.getResponseCode() == 200 ) {
     responseArray = JSON.parse(responseApi.getContentText());
     responseOk = responseArray.ok;
     if (responseOk == false) {
       if (responseArray.error == "rate limit") {
         Utilities.sleep(60000);
         Logger.log('Rate limit, Retry-After is unknown. Wait 60[s].')
       } else {
         Logger.log('Error: '+responseArray.error)
         responseOk = true;
       };
     };
   } else {
     waittime = parseInt(responseApi.getAllHeaders()["Retry-After"],10)
     Utilities.sleep(waittime*1000);
     Logger.log('Rate limit, Retry-After is '+waittime+'[s], Waiting.')
   }
 } while ( responseOk != true );

 return responseArray;
}​

シートのURLとシート名、tokenがあるセル名は自分で埋めます。

コード③ 毎朝未明に、最初のステータス設定をする時刻のトリガーを設定するルーチン

毎朝未明に起動するように設定し、その日のカレンダーを全部読み込んで、最も時間がはやい予定の開始時刻(=初回のSlackステータス設定時刻)にコード⑤を起動するトリガーを設定するルーチンです

// 処理本体
// 毎朝動かすもの
function setStatusTrigger() {
 var startDate = new Date();
 startDate.setHours(0, 0, 0, 0);
 var endDate = new Date();
 endDate.setHours(23, 59, 59, 999);
 var myevents = getScheduleCalendar(startDate, endDate);

 if (myevents.length > 0) {
   // 最初のカレンダーの開始時刻をみて初回を設定する
   ScriptApp.newTrigger("set_freeformat").timeBased().at(myevents[0].getStartTime()).create();
 } else {
   // 土日などはカレンダーゼロという前提
   setSlackStatus("weekend", endDate);
 }
}
コード④ Google Apps Scriptのトリガーを消すルーチン
このスクリプトでは任意の時刻に起動するトリガーを用いてSlackのステータス変更をしますが、使用後のトリガーを放置するとゴミのようにたまって悲しいことになるので、毎回けす必要がありますが、そのルーチンです

// Triggerの消去
function removeTrigger(function_name){
 // kickしたtriggerを消去する
 var triggers = ScriptApp.getProjectTriggers();
 for (var i = 0; i < triggers.length; i++) {
   if (triggers[i].getHandlerFunction() == function_name)
   {
     ScriptApp.deleteTrigger(triggers[i]);
   }
 }
}

コード⑤ ステータス設定時刻に呼び出される、ステータス設定と次の予定のトリガー設定を行うルーチン

まず、自分自身のトリガーを全部消した上で、カレンダーを再取得し、SlackのStatusを変え、その日の残りの予定の有無(予定の個数で確認、1個は必ずあるはずなので2個以上かどうかで確認)を確認して、あれば次の起動トリガーをかけます。

// 自由な設定を出来るようにするモード
function set_freeformat(){
 // kickしたtriggerを消去する
 removeTrigger("set_freeformat")

 // 起動された時間周辺のカレンダーを取得する
 var startDate = new Date();
 startDate.setMinutes(startDate.getMinutes()-5);
 var endDate = new Date();
 endDate.setHours(23, 59, 59, 999);
 var myevents = getScheduleCalendar(startDate, endDate);
 var status_text  = myevents[0].getTitle();
 var status_emoji = myevents[0].getDescription();
 var status_endtime = myevents[0].getEndTime();

 // Slackのstatusを変える
 setSlackStatus(status_endtime, status_text, status_emoji);

 // 次のカレンダーは入っているか?
 if (myevents.length > 1) {
   // あればこの関数のトリガーを設定する
   ScriptApp.newTrigger("set_freeformat"     ).timeBased().at(myevents[1].getStartTime()).create();
 }
};