GoogleカレンダーとSlackステータスを連携する【GAS】

この記事は『[Slack Advent Calendar 2019](https://qiita.com/advent-calendar/2019/slack)』の15日目の記事です。

## 作りたいもの

Googleカレンダーに予め予定を入れておき、予定の時間になると、Slackのステータスが切り替わるもの。

は、もう[既にあるようですね...](https://slack.com/intl/ja-jp/help/articles/206329808-google-calendar-for-slack#slack-%E3%81%A7-google-%E3%82%AB%E3%83%AC%E3%83%B3%E3%83%80%E3%83%BC%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%99%E3%82%8B)。

ただ、これを使用するには、Googleカレンダーから出席依頼を「承諾」することが必要で、ステータスも「In a meeeting」のみ。もっと他のこと（例えば有給休暇中とか）でステータスを切り替えたくなったり、会社がGoogleカレンダーを使っていないとなると、どうしても自分で作る必要が出てくる。

そこで自分でGoogleカレンダーとSlackステータスを連携させるものを作成して、実際の稼働までを書いてみる。

## 構成

今回はSlack Web APIとGoogle Calendarを、Google App Script（GAS）を使って連携させる。図にすると理想はこんな感じ。
![fig1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163288/078b6319-66c9-2a7a-add8-ee15a72744dc.png)
このようなものを作っている方は結構いらっしゃったが、POSTで投げている方はあまりいなかったので、今回はPOSTで投げてみる。

## GASコード

```js:SlackStatusWriter.gs
/**
 Googleカレンダーから取得した現在の予定をSlackステータス用に整形して返却する。
**/
function createStatusText(event) {
  // 整形した開始時刻・終了時刻
  var start = event.getStartTime().getHours() + ":" + ("00" + event.getStartTime().getMinutes()).slice(-2);
  var end = event.getEndTime().getHours() + ":" + ("00" + event.getEndTime().getMinutes()).slice(-2);
  // ステータステキスト
  var text = event.getTitle() + "(" + start + "〜" + end + ")";
  
  // イベントがある時のステータス
  var event_status = {
    "profile": JSON.stringify({
      "status_text": text,
      "status_emoji": ":spiral_calendar_pad:"
    })
  };
  
  return event_status;
  
}

/**
 作成したステータスをSlack Web API経由でプロフィールに反映させる。
**/
function postSlackStatus(status) {
  // アクセス情報
  const TOKEN = "(取得したSlack API トークン)";
  const URL = "https://slack.com/api/users.profile.set";
  
  // HTTPヘッダー
  const headers = {
    "Authorization" : "Bearer " + TOKEN
  };
  
  //POSTデータ
  var option = {
    "Content-Type": "application/json",
    "headers": headers,
    "method": "POST",
    "payload": status
  };
  
  var fetch = UrlFetchApp.fetch(URL, option);

}

/**
 カレンダーから今日の予定を取得し、必要であればSlackステータスを更新する。
**/
function main() {
  // カレンダーID
  const ID = "(Slackステータスに使うGoogleカレンダーID)";
  // 今日の日付
  var date = new Date();
  // カレンダーから今日の予定を取得
  var calendar = CalendarApp.getCalendarById(ID);
  var events = calendar.getEventsForDay(date);
  
  // 今日のイベントがない場合は何もしない
  if (events.length !== 0) {
    
    // イベントがないときのステータス
    var set_status = {
      "profile": JSON.stringify({
        "status_text": "暇暇の暇",
        "status_emoji": ":dancer:"
      })
    };
    
    // 今日の予定をすべて調査
    for (var i in events){
      // 今が予定の開始時刻以降で終了時刻以前なら今はその予定の最中 -> ステータス変更
      if (events[i].getStartTime() <= date && events[i].getEndTime() >= date) {
        set_status = createStatusText(events[i]);
        break;
      }
    }
    
    postSlackStatus(set_status);
    
  }
}
```
コード内の

* `(取得した取得したSlack API トークン)`
* `(Slackステータスに使うGoogleカレンダーID)`

は、別途自分で取得する必要がある。

また、時間を取得する際、分を

```js:
("00" + getMinutes()).slice(-2);
```

としておいたほうがいい。でないと、例えば17:00とかだと、「17:0」になってしまう。間違ってはないけど違和感がある…。

## アクセス情報の取得方法

### Slack API トークン

1. [Legacy tokens](https://api.slack.com/custom-integrations/legacy-tokens)へアクセスする。
 * なぜか見つけるのが大変なので、よく使うならブックマークしてもいいかも
2. Slackワークスペースにサインインしていれば、自分のワークスペースが表示される。まだトークンを作っていなければ、「Create Token」をクリックすれば作成できるので、これをコピーする。
	<img width="647" alt="fig2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163288/d73867bd-f9d7-7c14-834b-f17475830da1.png">

### GoogleカレンダーID

1. [Googleカレンダー](https://calendar.google.com/calendar)にアクセスし、Slackのステータスに使いたいカレンダーの「設定と共有」を開く。
	<img width="398" alt="fig3.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163288/7d66f01c-e3d6-446d-ebee-6ae066056406.png">
	
2. 「カレンダーの統合」を開き（もしくは下にスクロール）、カレンダーIDをコピーする。
	<img width="1030" alt="fig4.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163288/9bf2e372-a0ab-13f0-f16b-a0f63b081e51.png">


## GASコードを動かす

1. [G Suite Developer Hub](https://www.google.com/script/start/)にアクセスし、「Start Scripting」をクリックする。
2. Googleアカウントでログインすると、管理画面に飛ばされるので、「新しいプロジェクト」で新規作成する。
3. コードを実装し、保存する。

時間周期で稼働させる前に、一度実行ボタンから実行しておく。というのも、初回のみGoogleカレンダーへのアクセスの確認が必要なため。

実行時は、最初にどのメソッドを動かすかを指定して、実行する。
<img width="450" alt="fig5.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163288/616f059d-9f49-5280-d828-ee9e42ece11a.png">


### 時間周期で稼働させる

1. エディタ画面から「編集→現在のプロジェクトのトリガー」をクリックする。
2. 「トリガーの追加」をクリックする。
3. 以下のように設定する。
	
	|項目|設定値|
	|:---|:---|
	|1. 実行する関数を選択|`main`|
	|2. 実行するデプロイを選択|`Head`（しかないはず）|
	|3. イベントのソースを選択|`時間主導型`|
	|4. 時間ベースのトリガーのタイプを選択|`分ベースのタイマー`|
	|5. 時間の間隔を選択（分）|`1分おき`|
	
	4.と5.は「稼働させる時間の間隔」なので、ここはお好みで。横の「エラー通知設定」も好きなタイミングで受け取るようにすれば良い。
	
4. 「保存」をクリックする。

これで指定した時間周期でGASが実行される。

## お手並み拝見

こんな予定を立ててみる。
<img width="464" alt="fig6.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163288/eba9345d-0258-6ad1-9fe3-3a57d4daa073.png">
（この予定はフィクションです。）

### 予定開始前

<img width="397" alt="fig7.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163288/613b7bd2-7ea3-7ad2-4f6d-147250db60f6.png">

### 予定開始時刻

さぁ、嘘の課会議が始まりました。
<img width="395" alt="fig8.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163288/e76f77ba-1cea-e174-d92c-70c20b7433f6.png">
うん、いい感じ。

### 予定終了時刻

<img width="396" alt="fig9.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/163288/636278a9-6272-1e48-4841-d76ca2937e4a.png">
完璧ですな…

## GASの注意点

GASも無料で使えるだけあって色々制限がある。例えば今回のものだと、URL fetchの呼び出しは、1日で20,000回まで。また、トリガーの1日の総実行時間は90分以内でなければならない。今回のスクリプトは1分に1回動かしているから、1回の実行時間は3.75秒以内でないといけない。

詳しい制限内容は[こちら](https://developers.google.com/apps-script/guides/services/quotas)

とはいえ、1分に1回URL Fetchしたところで1日合計1,440回だし、今回のスクリプトは1回あたりの実行時間が長くても0.8秒ほどなので、割と制限はゆるゆるなのかもしれない。もう少し複雑なものになると実行時間だけ気をつける必要があるかも…。

ちなみに実行時間は、エディタ画面から「表示→実行トランスクリプト」で確認できる。

## 最後に

普通に楽しかった。もっと早くからGASを始めておけばよかったと思った。最後のほうは少しGASの話になってしまったけど、Slackへのリクエストがすごく簡単だったので、Slackを使っている方は合わせて覚えたほうがいいものだと思った。