毎日定刻に予定を知らせてくれるやつが欲しい

ラボでSlackを導入したところタイトルのような要望があったのでやってみました。
もともと#generalにGoogle Calender Appが予定前日に投稿してくれていたのですが、「当日朝にもDMがほしいけど設定ができない」との声があったという経緯があります。

本記事の目次

ラボの日程管理にGoogle Calenderを使っていたので、Google Apps Scriptを利用しました。

いるもの

  • Googleアカウント
  • 参照したいGoogle Calender
  • slack api token (scopeはusers:read, chat:write:bot あたりかな)
  • 投稿先のchannelまたはuserのID

slack appのインテグレーションとtoken取得は調べるといろいろ出てくるので割愛します。

流れとしては
 Google Apps Scriptのtriggerでスクリプト起動
→投稿する文章を形成
json形式でPOST request
→発言
といった感じになります。

user IDの取得

channelに対して投稿する場合はchannel名でいいけれど、個人にDMという形で投稿するならuser IDを取得する必要があります。
今回はひとりのIDを取得するだけだったので手軽にブラウザで

https://slack.com/api/users.list?token=<取得したtoken>

して探しました。

カレンダーの取得とイベントの取得

//カレンダー取得
var myCals = CalendarApp.getCalendarById(<カレンダーID>);
//todayを今日に設定
var today = new Date();
//今日の予定を取得
var myEvents = myCals.getEventsForDay(today);

<カレンダーID>には"*@group.calendar.google.com"を入力します。
これで今日の予定がmyEventsに格納されました。

POST bodyの整形

2/24 There are n events today
イベント名 12:00 at 2階ラウンジ 担当:maple
イベント名 17:00 at 302講義室 担当:おもち
~

こんな感じにしてほしいということだったのでこの通り整形していきます。

日時表記の整形

todayには

Sat Feb 24 12:19:40 GMT+09:00 2018

こんな感じで日時が入っているので、これを目的の形式にしていきます。
イベントの開始時刻もこれなので、併せて処理を書いてみました。

//日付表記を 'M/d' 形式に
function MMdd(date){
  return Utilities.formatDate(date, 'JST', 'M/d');
}

//時刻表記を 'HH:mm' 形式に
function HHmm(date){
  return Utilities.formatDate(date, 'JST', 'HH:mm');
}

1行目を決定

日付とイベントの数を記入

2/24 There are n events today

英語はisとかareとかあって面倒ですね。

var strBody = "*" + MMdd(today) + "* ";
if(myEvents.length == 0){
  strBody += "There are no events today";
}
else if(myEvents.length == 1){
  strBody += "There is *1* event today";
}
else{
  strBody += "There are *" + myEvents.length + "* events today";
}

2行目以降を決定

ゴリ押しです。

for(var i = 0; i < myEvents.length; i++){
  strBody += "\n*" + myEvents[i].getTitle();
  strBody += "* " + HHmm(myEvents[i].getStartTime());
  strBody += " at " + myEvents[i].getLocation();
  strBody += " 担当:" + myEvents[i].getDescription();
}

POST requestする

Google Apps ScriptではUrlFetchAppクラスでHTTPリクエストが定義されています。
Argumentsのkeyはslack側の指定に従います(slack API methods|chat.postMessage 参照)

//json形式に
var Arguments = {
  token : <取得したtoken>,
  channel : <channel名またはユーザーID>,
  text : strBody
};

//POST指定とArgumentsをpayloadに指定  
var options = {
  'method' : 'post',
  'payload' : Arguments
};
  
//以上の設定でPOSTリクエストを送信
var url = 'https://slack.com/api/chat.postMessage';
var response = UrlFetchApp.fetch(url, options);
Logger.log(response.getContentText());

ここまででとりあえず予定を取得して投稿ができるようになりました。
次は毎日定刻に動くようにトリガーを設定します。

定刻に動くようにする

GASのトリガーって実はよくわからなくて、"日タイマー"で毎日9時って設定すると9時~10時の間のいつかに開始する、っていう感じになってしまうんです
しかし、"特定の日時"で時刻もきっちり指定できるという謎仕様。
この"特定の日時"指定は1回で使い捨てなので、少し工夫して使ってみました。

整理すると、
 毎日、目的の時間より前にトリガーセットスクリプトを起動
→トリガーセットスクリプトがその日の9時に"特定の日時"指定でトリガーをセット
→そのトリガーに従ってリマインダースクリプトが起動
→slackに投稿される
という流れになります。
トリガーは2回発動するというわけです。

function setTrigger() {
  var triggerDay = new Date();
  triggerDay.setHours(9);
  triggerDay.setMinutes(0);
  ScriptApp.newTrigger("Reminder").timeBased().at(triggerDay).create();
}

この関数について"日タイマー"で7時とかでトリガーを設定しておけばOKです。
これで作製したトリガーは日々たまってしまうので、リマインダースクリプト自体にトリガーを削除する文を入れておくと吉です。

function deleteTrigger() {
  var triggers = ScriptApp.getProjectTriggers();
  for(var i=0; i < triggers.length; i++) {
    if (triggers[i].getHandlerFunction() == "Reminder") {
      ScriptApp.deleteTrigger(triggers[i]);
    }
  }
}

以上で一応動くものができました。

感想

ドキュメントを読んだり試行錯誤したりで勉強になったし、毎日誰かの役に立っているということでよかったなと思っています。
ただ、ワークスペースの管理者ならGoogle Calender AppをいじってユーザーにDM通知ができるらしいです。
後から発覚して悲しい気持ちになりました。
しかしながらこちらはこちらで、好きにテキストを整形できるところや特定の曜日に投稿しないなどの調節が簡単なのは利点かなと思います。
Goole Apps Script なんでもできそうなのでまたいろいろやってみたいです。