GASを使ってGoogleカレンダーにカレンダーを追加する

# はじめに
コロナということもあって外出する機会も減り、仕事もリモートワークで
体を動かさなくなってきてしまった今日このごろですね。

以前はYouTubeでトレーニング動画をみて家でやってたのですが、半年で続かなくなっちゃいましたね。
そんな中、友人に誘われたので一緒にジムに入ることにしました。

どうせ入るなら元を取りたいし、どれくらい行ってるのかも気になるので
「いつ行ったかをメモろう！」ってなってたんですけど
毎回 "Y年m月d日 H:i:s 〜" って記録するのが面倒なので自動化したいな〜と思い、作るに至りました。

# 何を使って自動化するか
## Googleカレンダー
結局のところ

- 日付
- 時間
- 場所

をメモれるものってカレンダー系のアプリしかないな〜って思ったんですよね
普段から予定管理に使っているのもあって

:::note info
Googleカレンダーでメモしよう！
:::


となりました

## Google App Script
Googleカレンダーを操作するならやっぱり同じGoogleが開発してるGASがいいのだろうとなって


:::note info
GASを使おう！
:::
となりました

# どうやって自動化するか
## GASのWebアプリ化
ボタン1つで打刻できれば良い = アクセスがきたらデータを追加
と置き換えれば話は早いです

## 仕組みを考える
カレンダーの追加って

- タイトル
- 開始時刻
- 終了時刻

が必要で、
入室時に1回、退室時に1回と押すとしたら
退室時間がわかってから記録するようにしなければなりませんが、
「入室時の時間ってどこにメモっておけば...」が障害になりました

そこで調べてみたら、GASの機能に
**[Cache Service](https://developers.google.com/apps-script/reference/cache)** というものがありました

キャッシュを最長で6時間保持しておけるようなのでこれならいけそうです
※6時間以上トレーニングする人は他のやつ使ってください 笑

# 実装
## アクセスできる用の関数を用意
アクセス時に呼び出される関数は名前が決まっていますので、今回はGET通信で呼び出されるdoGetを使用します

```js:doGet.gs
//GET通信時に呼び出される
function doGet(){
}
```
## クエリ文字列を取得
GASでクエリ文字列を取得する方法は以下の通りです
今回は入退室フラグとしてEoLFlg（Enter or Leave Flag）を使います

```js:doGet.gs
function doGet(e){
  var EoLFlg = e.parameter.EoLFlg;
}
```
## 追加するカレンダーの取得
今回は「ジム」というカレンダーを作りました
カレンダーIDからそのカレンダーのオブジェクトを取得できるので、そのように記載します

```js:doGet.gs
const calendarId = '[対象カレンダーのカレンダーID]←カレンダーから 設定>マイカレンダーの設定>カレンダーの統合から確認できます';
const calendar = CalendarApp.getCalendarById(calendarId);
```

カレンダーのクラスの使い方を詳しく知りたい方は以下の公式ドキュメントへどうぞ
https://developers.google.com/apps-script/reference/calendar/calendar-app
##キャッシュを使う準備
キャッシュを使えるように関数を準備します
※他の方のコードの引用なので、最後に参考文献として載せてあります


```js:makeCache.gs
// キャッシュの保存・取得をする関数 ※Qiitaからの引用（https://qiita.com/golyat/items/ba5d9ce38ec3308d3757）
function makeCache() {
  const cache = CacheService.getScriptCache();
  return {
    get: function(key) {
      return JSON.parse(cache.get(key));
    },
    put: function(key, value) {
      cache.put(key, JSON.stringify(value), 21600);
      return value;
    }
  };
}
```

# 実装（コード）
以上で完了です！
定数を変えればほぼコピペで使えるソースを下記に載せておきますね
細かく説明を載せておきますので、コピペして使う前に確認をしてください

```js:addCalendar.gs
// 定数の宣言（カレンダーID、カレンダーオブジェクト、追加するカレンダーのタイトル、場所）
const calendarId = '{カレンダーID}';
const calendar = CalendarApp.getCalendarById(calendarId);
const title = '{タイトル}';
const location = '{場所}';
const option = {'location':location};

// キャッシュ周りの定数（保存時間、関数、キーとして使用するフラグの値）
const cacheTime = 21600;
const cache = makeCache();
const EoLFlgs = ['E', 'L'];

// GETでのアクセス時に対応
function doGet(e){
  var EoLFlg = e.parameter.EoLFlg;

  // EoLFlgが E 又は Lの時に起動
  if( EoLFlgs.indexOf(EoLFlg) != -1){
    var date = new Date();
    cache.put(EoLFlg, Utilities.formatDate( date, 'Asia/Tokyo', 'YYYY/MM/dd H:m:s'));

    switch(EoLFlg){
      // E：入室時 念の為退室時刻のキャッシュをクリア
      case "E":
      cache.put('L', null);
      break;

      // L:退室時 入室時刻 < 退室時刻 となっていればカレンダーに追加
      case 'L':
      var startTime = cache.get(EoLFlgs[0]);
      var endTime = cache.get(EoLFlgs[1]);
      cacheClear();
      if( startTime < endTime ){
        var result = calendar.createEvent(title, new Date(startTime), new Date(endTime), option);
      }else{
        return HtmlService.createTemplateFromFile('ERROR').evaluate();
      }
      break;
    }
    return HtmlService.createTemplateFromFile('success'+EoLFlg).evaluate();
  }
}

// キャッシュの保存・取得をする関数 ※Qiitaからの引用（https://qiita.com/golyat/items/ba5d9ce38ec3308d3757）
function makeCache() {
  const cache = CacheService.getScriptCache();
  return {
    get: function(key) {
      return JSON.parse(cache.get(key));
    },
    put: function(key, value) {
      cache.put(key, JSON.stringify(value), cacheTime);
      return value;
    }
  };
}

// キャッシュクリア用関数
function cacheClear(){
  // それぞれのキャッシュをクリア
  for(var i = 0; i < EoLFlgs.length; i++){
    cache.put(EoLFlgs[i], null);
  }
}
```

### 定数
```js:addCalendar.gs
// 定数の宣言（カレンダーID、カレンダーオブジェクト、追加するカレンダーのタイトル、場所）
const calendarId = '{カレンダーID}';
const calendar = CalendarApp.getCalendarById(calendarId);
const title = '{タイトル}';
const location = '{場所}';
const option = {'location':location};
```
##### calendarId
追加するカレンダーのID
Googleカレンダーの設定画面に書かれているものをいれる（図のオレンジの箇所）
![スクリーンショット 2021-03-21 21.14.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/612137/321ddc53-f753-8e35-acf8-d68954d0ef62.png)
##### title
カレンダーのタイトル
![スクリーンショット 2021-03-21 21.22.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/612137/4c3b3b4e-5562-7161-8bcd-02479b5d0348.png)
##### location
カレンダーに設定される場所
![スクリーンショット 2021-03-21 21.24.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/612137/b4ac95a4-2d26-690d-cdaf-4e7f670a6d5f.png)
※optionで説明等も追加することができます

### 定数（キャッシュ等）
```js:addCalendar.gs
// キャッシュ周りの定数（保存時間、関数、キーとして使用するフラグの値）
const cacheTime = 21600;
const cache = makeCache();
const EoLFlgs = ['E', 'L'];
```
##### cacheTime
キャッシュの保存時間　最大6時間（21600秒）　※makeCacheで使用
##### cache
makeCacheの定数化
##### EoLFlgs
クエリ文字列で使用するEoLFlgの配列
E = Enter（入室）
L = Leave（退室）
のフラグとして使用

### doGet
```js:addCalendar.gs
// GETでのアクセス時に対応
function doGet(e){
  var EoLFlg = e.parameter.EoLFlg;

  // EoLFlgが E 又は Lの時に起動
  if( EoLFlgs.indexOf(EoLFlg) != -1){
    var date = new Date();
    cache.put(EoLFlg, Utilities.formatDate( date, 'Asia/Tokyo', 'YYYY/MM/dd H:m:s'));

    switch(EoLFlg){
      // E：入室時 念の為退室時刻のキャッシュをクリア
      case "E":
      cache.put('L', null);
      break;

      // L:退室時 入室時刻 < 退室時刻 となっていればカレンダーに追加
      case 'L':
      var startTime = cache.get(EoLFlgs[0]);
      var endTime = cache.get(EoLFlgs[1]);
      cacheClear();
      if( startTime < endTime ){
        var result = calendar.createEvent(title, new Date(startTime), new Date(endTime), option);
      }else{
        return HtmlService.createTemplateFromFile('ERROR').evaluate();
      }
      break;
    }
    return HtmlService.createTemplateFromFile('success'+EoLFlg).evaluate();
  }
}
```

##### var EoLFlg = e.parameter.EoLFlg;
クエリ文字列から取得したEoLFlg
##### 流れの説明
1. EoLFlgの値が" E" もしくは "L"だった場合にのみ起動
2. 起動時の時刻をキャッシュとして保存
3. Eの場合（入室時）：Lのキャッシュをクリア
4. Lの場合（退室時）：それぞれのキャッシュ情報を取得後、キャッシュクリア
5. EのキャッシュがLのキャッシュより過去日になっていれば、カレンダーに追加
6. EのキャッシュがLのキャッシュより過去日になっていなければ、エラー表示
7. 正常に処理が終われば成功の表示

***
return HtmlService.createTemplateFromFile('ERROR').evaluate();
return HtmlService.createTemplateFromFile('success'+EoLFlg).evaluate();
はエラー時、成功時にHTMLを出力するための記述なので、
HTMLを3ファイル追加してください
* ERROR.html
* successE.html
* successL.html
***

### キャッシュ操作の関数
```js:addCalendar.gs
// キャッシュの保存・取得をする関数 ※Qiitaからの引用（https://qiita.com/golyat/items/ba5d9ce38ec3308d3757）
function makeCache() {
  const cache = CacheService.getScriptCache();
  return {
    get: function(key) {
      return JSON.parse(cache.get(key));
    },
    put: function(key, value) {
      cache.put(key, JSON.stringify(value), cacheTime);
      return value;
    }
  };
}

// キャッシュクリア用関数
function cacheClear(){
  // それぞれのキャッシュをクリア
  for(var i = 0; i < EoLFlgs.length; i++){
    cache.put(EoLFlgs[i], null);
  }
}
```

##### makeCache
キャッシュを操作する関数
cache.get(key)で引数のキーのキャッシュを取得できる
cache.put(key, value)で引数のキーのキャッシュを設定できる
※上記の定数で指定したchcheTimeはここで使う
##### cacheClear
キャッシュクリアする関数
EoLFlgsのループをし、nullを入れる

以上が処理の流れと実装内容になります

# さいごに
今回はGoogle App Scriptを用いてGoogleカレンダーにカレンダーを追加する方法について書きました
勉強のメモとして書きましたが、GASを使ってなにかしたいと思っている人の参考になれば嬉しいです

最後までお読みいただきありがとうございました


# 参考文献
- Cache Serviceを詳しく説明してくれている方の記事

https://qiita.com/golyat/items/ba5d9ce38ec3308d3757

- タイムゾーンの変更方法について詳しく説明してくれている方の記事

https://qiita.com/park3taro/items/a4c61305386e151a6529

# 備考
自分はiPhoneのショートカットで
「ジムに到着したら」「ジムを出発したら」ってオートメーションに
今回作ったWebアプリにアクセスするショートカットを実行できるようにして使ってます