GASで課題や出席の予定を自動的に追加

# はじめに
この記事は[SLP KBIT Advent Calendar 2022](https://adventar.org/calendars/7569)、21日目です。

今回というか今年の4月ごろから作っていたのですが、「Kadai-Calendar」を紹介します。
開発のきっかけは大学の課題を提出し忘れることが多かったからです。授業によっては今日まで、週末まで、授業の前日までのように、どの課題がいつまでに提出すればよいのか覚えられない、あるいは忘れてしまうかもしれません。
「Kadai-Calendar」はGASを使ってスレイピングし、Googleカレンダーに自動的に追加してくれるシステムです。Googleカレンダーに予定が追加されることで、スマホから予定が通知されたり、Googleカレンダーから直接Moodleのイベントにアクセスすることができます。

# 制作手順
## GASを使ってログインする
### ログイン画面表示のリクエスト
まずはログイン処理のリクエストに必要なCookieを取得するため、ログイン画面表示のリクエストを行い、そのレスポンスからCookieを取得します。ログイン処理のリクエストに必要なCookieは全て必要というわけではなく、特定の情報だけでOKでした。今回、必要なCookieは``MoodleSession``のみであったため、リクエストヘッダーの``Set-Cookie``から値を取得しました。
```js
  // ログインページを開く(GET) <200>
  headers = {
    'user-agent': user_agent,
  }
  options = {
    'headers': headers,
  }
  response = UrlFetchApp.fetch('<MoodleのログインURL>', options);
```

### ログイン処理のリクエスト
レスポンスはPOSTとなっており、Payloadには``usernam``,``passwor``,``logintoken``がありました。``logintoken``は先ほどのログイン画面表示のレスポンスのHTMLタグ内にあったため、cheerioと呼ばれる、スレイピングなどで利用されるGAS特有のライブラリを使って値を取得しました。では先ほど取得したCookieを含め、これらをもとにログイン処理を実装していきます。
```js
  // ログインフォーム送信(POST) <303>
  headers = {
    'cookie': moodleSession,
    'user-agent': user_agent,
  }
  payload = {
    'logintoken': logintoken,
    'username': username,
    'password': password,
  }
  options = {
    'method': 'post',
    'headers': headers,
    'payload': payload,
    'followRedirects': false,
  }
  response = UrlFetchApp.fetch('<MoodleのログインURL>', options);
```
ここでの注意点として、１つ目はブラウザからのアクセスであると偽装するために、送信元のユーザーエージェントを指定しておくこと(意味ないかも)、２つ目はリダイレクト処理を行ってしまうと取得される情報がログイン処理のレスポンスではなく、ログイン画面のレスポンスになってしまうため``followRedirect``は``false``にしておきます。

### カレンダー画面表示のリクエスト
最後にカレンダー画面表示のリクエストヘッダーにログイン処理後のCookieとユーザーエージェントを指定し、GETメソッドでレスポンスを行います。URLはカレンダーのページを指定しておきます。
```js
  headers = {
    'cookie': moodleSession,
    'user-agent': user_agent,
  }
  options = {
    'method': 'get',
    'headers': headers,
  }
  response = UrlFetchApp.fetch('<Moodleの直近イベントのURL>', options);
```

## 取得したソースを解析する
必要な情報は先ほども登場したcheerioで取得していきます。抽出する情報はイベントの概要、説明、期限、教科名、URLなどがあります。またGoogleカレンダーに自動追加を行うときにイベントの重複確認を行う必要があるため、運よくソース内にあったIDも抽出しておきます。
抽出が終わったら、Googleカレンダーにきれいに落とし込めるように、抽出した情報を結合などして、最終的にはタイトル、時間、色、説明といった感じに整理します。このように分けた理由としては次の節で後述しています。
![画像1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2355049/075a9c8a-bea3-528a-b882-d4ae5cc62fda.png)


## Googleカレンダーに追加する
Googleカレンダーに追加する前に重複確認を行っておきます。Googleカレンダーのタイトルを取得し、タイトル内にあるIDとMoodleから取得したIDを比較し、同じであればスキップ、そうでなければ追加していきます。
次に、先ほど整理したイベントの情報を以下のコードのように書いていきます。これを実行することで、下の図のようにGoogleカレンダーとして追加できました。あとはGASのトリガーで一日一回、更新する時間を指定しておくことで今後自動的に提出物のイベントが追加されます。
```calendar.js
    function create_event(eventObj, myCalendarObj) {
      let title = eventObj.subjectTitle + '[' + eventObj.id + ']';

      // 時間を指定
      let startTime;
      switch (eventObj.classNum) {
        case 1: startTime = new Date(eventObj.eventtime.getFullYear(), eventObj.eventtime.getMonth(), eventObj.eventtime.getDate(),  8, 50, 0); break;
        case 2: startTime = new Date(eventObj.eventtime.getFullYear(), eventObj.eventtime.getMonth(), eventObj.eventtime.getDate(), 10, 30, 0); break;
        case 3: startTime = new Date(eventObj.eventtime.getFullYear(), eventObj.eventtime.getMonth(), eventObj.eventtime.getDate(), 13, 00, 0); break;
        case 4: startTime = new Date(eventObj.eventtime.getFullYear(), eventObj.eventtime.getMonth(), eventObj.eventtime.getDate(), 14, 40, 0); break;
        case 5: startTime = new Date(eventObj.eventtime.getFullYear(), eventObj.eventtime.getMonth(), eventObj.eventtime.getDate(), 16, 20, 0); break;
        default : startTime = eventObj.eventtime; break;
      }
      let endTime = eventObj.eventtime;

      // イベントの説明
      let options = {description: eventObj.content};

      // イベントを追加
      let newEvent = myCalendarObj.createEvent(title, startTime, endTime, options);

      // 追加したイベントの色を指定
      switch (eventObj.component) {
        case 'mod_assign'       : newEvent.setColor(11); break;
        case 'mod_attendance'   : newEvent.setColor(10); break;
        case 'mod_quiz'         : newEvent.setColor(5);  break;
        case 'mod_questionnaire': newEvent.setColor(8);  break;
        default                 : newEvent.setColor(7);  break;
      }
    }
```
![画像2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2355049/5db34d92-4240-c3f6-afa2-f7e139364aee.png)
※画像はだいぶ前のやつなのでコードと異なる部分があります

# 最後に
今回作ったカレンダー自動化は、便利すぎて非常に満足しています。Googleカレンダーはスマホにもインストールでき、締め切りの１時間前に通知が来たり、今後の予定を一目で確認したりできるため、このシステムを開発してから提出物の出し忘れがありません。
記事は主にバックエンド側について書いたため、Webアプリなどについては割愛しました。（時間がなかったので...）
一応、完成したやつ「Kadai-Calendar」(ベータ版)のリンクを貼っておきます。よかったら使ってみてください。ソースコードも貼っときます。

https://sites.google.com/view/kadai-calendar

https://github.com/tsukasa0220/Kadai-Calendar