Google Calendarの通知をSlackへPostするBotをGASで作ってみた

本日の予定一覧をつぶやくbotは多いけど、予定に設定してある通知の時刻になったら教えてくれるbotが見当たらなかったので GAS(Googls Apps Script) で作ってみた。  
(Slack の Integration API に Google Calendar はあるんだけど、これも予定の時間になったら教えてくれるだけで、５分前とか教えてくれない)

チーム全体のカレンダーを登録して使用する事を想定しています。  
例えば、午前９時〜１２時に「午前中停電の為、出社できません」というイベントを作り、ポップアップ通知を前日の９時と１８時に設定しておくと、以下のメッセージが表示されます。（たぶんきっと）

```
「明日は午前中停電の為、出社できません」
```

会議の時間は忘れなくても、こういうのウッカリしがち。  
もちろん会議の５分前に通知を出す事も出来ますよ。(４分前ぐらいになるかもだけど)

### 注意事項

GASへ貼り付ける時は Javascriptへコンパイルしてご使用ください。Chrome 上の CoffeeConsole がお手軽かな？

そもそもトリガが毎分起動じゃなく１分毎起動なので１分ほど遅れて通知が来る事もしばしばです。また、GASサーバーがそんなキッチリ働くかもわからないので、その辺を勘案の上ご利用ください。

シートのB1の場所に前回処理した時間を取っておいて、それ以降現在までに起きた通知を見つけて出力するようにしています。なので、スプレッドシートベースでの作成が必要です。  
また、妥当性のチェックは入れてませんので、勝手に書き換えるとどうなっても知りません。

終日予定の通知の取得方法が分からなかった為、それに関しては通知されません。

使用は自己責任でお願いします。

### 処理説明

1. Calendar オブジェクトを取得
1. 一週間先までのイベントを全て取得
1. 各イベントのポップアップ通知を全て取得
1. 前回の処理から現在までの間にポップアップすべきものがあれば Slack へ Post
1. その時にイイカンジのメッセージが出る様にゴリゴリ


#### 設定方法

1. Googleマイドライブ上で新規にスプレッドシート作成
1. [ツール]-[スクリプトエディタ] でエディタ起動
1. Javascript化したソースを貼り付ける。
1. SlackApp をライブラリに追加。こことか参考に-> [Slack BotをGASでいい感じで書くためのライブラリを作った](http://qiita.com/soundTricker/items/43267609a870fc9c7453)
1. ソース先頭の各種リテラル (Slackのtoken、投稿するチャンネルのid、カレンダーid)  を適宜設定する(取得方法はぐぐってね)
1. [リソース]-[現在のプロジェクトのトリガー] からトリガーをdoPostに設定する。  
このとき、coffeescriptからコンパイルしたソースだとdoPostメソッドが見つからないというエラーになる場合がある。その場合はdoPost を変数からメソッドへ変更する。

JavaScript ソース上で、これを

```js
doPost = function() {
```

こうする

```js
function doPost() {
```

### 使用ライブラリ

- [SlackApp](http://qiita.com/soundTricker/items/43267609a870fc9c7453)

### ソース

- [github.com/ykatsu/event-notify-bot](https://github.com/ykatsu/event-notify-bot)

```coffeescript:event-notify-bot.coffee
token = '<<自分のSlackのtoken>>' # Slackのtoken
channel_id = '<<投稿するチャンネルのid>>'  # チャンネルID https://api.slack.com/methods/channels.list/testで調べられる
calendar_id = '<<カレンダーid>>'
bot_name = 'notify'
bot_icon = ':robot_face:'

isDebug = true

###
Post処理メイン
###
doPost = ->
  now = new Date()
  endTime = new Date(now)
  endTime.setDate(endTime.getDate() + 7) # 1週間先までの予定の通知を調べます
  message = listupEventNotify(calendar_id, now, endTime)
  Logger.log message
  postSlack(message)
  return

###
通知をリストアップ
###
listupEventNotify = (cal_id, now, endTime) ->
  # 前回処理した時間を取得し、現在の時間を保存
  prevTime = getPrevTmve()
  nt = fd(now)
  setCurTime(nt)

  list = []
  events = CalendarApp.getCalendarById(cal_id).getEvents(now, endTime)
  if isDebug
    n = fd(now)
    e = fd(endTime)
    Logger.log "now = #{n}, end = #{e}"
    Logger.log 'Number of events: ' + events.length

  for event in events
    reminders = event.getPopupReminders() # 終日予定はなぜかこれが取れない
    remindHits = []
    for remindMinutesTo in reminders
      eventTime = event.getStartTime()
      remindTime = new Date(eventTime)
      remindTime.setTime(eventTime.getTime() - remindMinutesTo*60000)

      if isDebug
        rt = fd(remindTime)
        pt = fd(prevTime)
        Logger.log "prev:#{pt}, remind:#{rt}, now:#{nt}"

      if prevTime < remindTime <= now
        remindHits.push(remindMinutesTo)

    if remindHits.length >= 1
      # 通知が複数該当する場合は、直近のものだけ通知
      minutesTo = Math.min.apply(null, remindHits)
      Logger.log "minhit:#{minutesTo} in #{remindHits.join(',')}" if isDebug
      list.push(generateNotifyMessage(event.getTitle(), minutesTo, eventTime))

  text = ''
  for l in list
    text += l + '\n'
  text

###
日付フォーマット
###
fd = (time) ->
  Utilities.formatDate(time, 'GMT+0900', 'yyyy/MM/dd HH:mm:ss')

###
シート操作関連
###
setCurTime = (nt) ->
  getSheet().getRange(1,2,1,1).setValue(nt)

getPrevTmve = ->
  prev = getSheet().getRange(1,2,1,1).getValue()
  if prev == ''
    prev = new Date()
    prev.setDate(prev.getDate() - 1)
  prev

getSheet = ->
  if getSheet.sheet
    return getSheet.sheet
  getSheet.sheet = SpreadsheetApp.getActive().getSheetByName('シート1');

###
通知イベント表示用メッセージを作成
###
generateNotifyMessage = (title, minutesTo, eventTime) ->
  remindTime = new Date(eventTime.getTime() - minutesTo*60000)
  dayDiff = calcDayDiff(eventTime, remindTime)
  if minutesTo <= 0
    mes = "#{title}の時間です。"
  else if (dayDiff == 1)
    mes = "明日は、#{title}です。"
  else if (dayDiff >= 2)
    mes = "#{title}の#{dayDiff}日前です。"
  else if minutesTo < 60
    mes = "#{title}の#{minutesTo}分前です。"
  else
    mes = "#{title}の#{Math.round(minutesTo/60)}時間前です。"
  mes

calcDayDiff = (eventTime, remindTime) ->
  eventDay  = (eventTime.getTime()  + (1000 * 60 * 60 * 9))/(1000 * 60 * 60 * 24) >> 0
  remindDay = (remindTime.getTime() + (1000 * 60 * 60 * 9))/(1000 * 60 * 60 * 24) >> 0
  Logger.log "eventDay:#{eventDay}, remindDay:#{remindDay}" if isDebug
  eventDay - remindDay

###
Slackへポスト
###
postSlack = (payload) ->
  if payload.trim() == ''
    return

  app = SlackApp.create(token)
  app.postMessage(channel_id, payload,
    username: bot_name
    icon_emoji: bot_icon)
  return
```