大学の授業をGASに代返させる

# 注意

この記事は代返を推奨、賛同するものではありません。GASやCookie等の技術理解を目的としています。また、オンライン授業では、Zoom等のログや課題提出での出席確認やそもそも出席点を成績に加味しない場合が多いので本記事の内容だけで出席になることは稀です。
スクリプトを使用される際は自己責任でご使用ください。

# はじめに

私の大学では、出席は紙の出席カードか[Moodle](https://ja.wikipedia.org/wiki/%E3%83%A0%E3%83%BC%E3%83%89%E3%83%AB)等の授業支援システムで取られますが、最近はもっぱらMoodle等で出席が取られています。

具体的には授業開始時にMoodleの授業ページへのアクセスをもって出席とカウントされる仕組みです。

ですが、きちんとZoom等で授業を聞いているのに授業ページにアクセスし忘れて遅刻や欠席扱いになったり、授業前にアクセスしていて授業開始後にリロードしなかったために欠席扱いになったりとあまり優れたものではありません。

そこで**GoogleAppsScript**で出席を自動化し、こうしたミスを殲滅します。

# 手順

授業ページへのアクセスの前にログインの必要があり、その処理がこのスクリプトのほとんどです。

1. Moodleのログイン処理の流れを把握
1. ログイン処理の流れに従ってコードを記述
1. トリガーを設定

## 1. Moodleのログイン処理の流れを把握

以下の記事を参考にMoodleのログインの流れを確認しました。というか本記事の大部分はこちらの記事の受け売りです。
https://qiita.com/shikumiya_hata/items/aa7d80a1d17ca48ce079

私の大学のMoodleでは

1. ログインページをGET
1. レスポンスからログイントークン・セッション用cookieを取得し、ログインページにユーザー名・パスワードとともにPOST
1. レスポンスから新たなセッション用cookieを取得し、授業ページをGET

の流れで出席ログが取られることがわかりました。

## 2. ログイン処理の流れに従ってコードを記述

[GoogleAppsScript](https://script.google.com/home)にアクセスしてプロジェクトを新規作成します。
編集画面が出たら、同じく先ほどの記事を参考にスクリプトを記述します。

なお、リソースメニューからライブラリ(cheerio)を追加しないとエラーになります。


```js
// 13KJxU8q0ZYmZXyQswU2HrkQX-yXlgnlJ3BVzsKrS69oaE4FcViPRFPZb
const cheerio = libpack.cheerio()

/**
 * Cookieのユーティリティクラス
 */
class CookieUtil {
  /**
   * 値を抽出
   * @param {string} cookie Cookieデータ（"name=value;...")
   * @return {string} value
   */
  static getValue(cookies, key) {
    const cookiesArray = cookies.split(';');

    for(const c of cookiesArray){
      const cArray = c.split('=');
      if(cArray[0] == key){
        return cArray[1]
      }
    }

    return false
  }
}

function login() {
  const me = this
  
  let response, cookies, content, $, headers, payload, options, cookie_MoodleSession, location

  // ----------
  // ログインページを開く(GET)

  // リクエスト送信
  response = UrlFetchApp.fetch('ログインページのURL')

  // レスポンスヘッダーからCookieを取得
  cookies = response.getHeaders()["Set-Cookie"];
  // Cookieからcookie_MoodleSessionを取得
  cookie_MoodleSession = CookieUtil.getValue(cookies, 'MoodleSession')
  
  // コンテンツ(ログインページのHTML)を取得
  content = response.getContentText("UTF-8")

  //ログイントークンを取得  
  $ = cheerio.load(content)
  const token = $('[name="logintoken"]').val()
  
  // ----------
  // ログインフォーム送信(POST)
  headers = {
    'cookie': 'MoodleSession=' + cookie_MoodleSession + ';',
    'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36'
  }
  payload = {
    'anchor': '',
    'logintoken': token,
    'username': 'ユーザー名',
    'password': 'パスワード'
  }
  options = {
    'method': 'post',
    'headers': headers,
    'payload': payload,
    'followRedirects': false
  }
  response = UrlFetchApp.fetch('ログインページのURL', options)
  
  // レスポンスヘッダーから新たなCookieを取得(Set-Cookieが2つあるためgetAllHeadersで両方取得)
  cookies = response.getAllHeaders()["Set-Cookie"];
  cookies = cookies.toString()
  // Cookieからcookie_MoodleSessionを取得
  cookie_MoodleSession = CookieUtil.getValue(cookies, 'MoodleSession')
  
  //授業用ページを開く(GET)
  headers = {
    'cookie': 'MoodleSession=' + cookie_MoodleSession + ';',
    'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36'
  }
  options = {
    'method': 'get',
    'headers': headers,
    'followRedirects': false
  }
  response = UrlFetchApp.fetch('授業ページのURL', options)
  
  //デバッグ用(ログインが成功しているか、授業ページのタイトルを確認)
  /*content = response.getContentText("UTF-8")
  
  $ = cheerio.load(content)
  
  let page_title = $("title").text()
  console.log(page_title)*/
}
```
```class CookieUtil```はそのまま使用させていただきました。

なお、初回実行の際には承認が必要です。以下の記事が参考になります。
https://auto-worker.com/blog/?p=609

## 3. トリガーを設定

毎週同じ時刻に自動的に実行するようにします。例えば、毎週月曜１０時からの授業の場合、月曜日の10:02分くらいにアクセスできれば良いでしょう。
GASには定期的にスクリプトを実行してくれるように設定できますが、分単位での設定はできません。そこでこの設定もスクリプト内に組み込みます。
以下の記事を参考にしました。
https://www.pnkts.net/2019/09/23/gas-set-trigger

```js

function setTrigger() {
 var setTime = new Date();
  //スクリプト実行の7日後の10:02に再度実行するよう設定
  setTime.setDate(setTime.getDate() + 7)
  setTime.setHours(10);
  setTime.setMinutes(02); 
  ScriptApp.newTrigger('myFunction').timeBased().at(setTime).create();  
}
```

最後にどっちの関数も実行できるよう```myFunction()```をつけて完成です。

```js:完成形

// 13KJxU8q0ZYmZXyQswU2HrkQX-yXlgnlJ3BVzsKrS69oaE4FcViPRFPZb
const cheerio = libpack.cheerio()

function myFunction() {
  setTrigger();
  login();
}

function setTrigger() {
 var setTime = new Date();
  //スクリプト実行の7日後の10:02に再度実行するよう設定
  setTime.setDate(setTime.getDate() + 7)
  setTime.setHours(10);
  setTime.setMinutes(02); 
  ScriptApp.newTrigger('myFunction').timeBased().at(setTime).create();  
}

/**
 * Cookieのユーティリティクラス
 */
class CookieUtil {
  /**
   * 値を抽出
   * @param {string} cookie Cookieデータ（"name=value;...")
   * @return {string} value
   */
  static getValue(cookies, key) {
    const cookiesArray = cookies.split(';');

    for(const c of cookiesArray){
      const cArray = c.split('=');
      if(cArray[0] == key){
        return cArray[1]
      }
    }

    return false
  }
}

function login() {
  const me = this
  
  let response, cookies, content, $, headers, payload, options, cookie_MoodleSession, location

  // ----------
  // ログインページを開く(GET)

  // リクエスト送信
  response = UrlFetchApp.fetch('ログインページのURL')

  // レスポンスヘッダーからCookieを取得
  cookies = response.getHeaders()["Set-Cookie"];
  // Cookieからcookie_MoodleSessionを取得
  cookie_MoodleSession = CookieUtil.getValue(cookies, 'MoodleSession')
  
  // コンテンツ(ログインページのHTML)を取得
  content = response.getContentText("UTF-8")

  //ログイントークンを取得  
  $ = cheerio.load(content)
  const token = $('[name="logintoken"]').val()
  
  // ----------
  // ログインフォーム送信(POST)
  headers = {
    'cookie': 'MoodleSession=' + cookie_MoodleSession + ';',
    'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36'
  }
  payload = {
    'anchor': '',
    'logintoken': token,
    'username': 'ユーザー名',
    'password': 'パスワード'
  }
  options = {
    'method': 'post',
    'headers': headers,
    'payload': payload,
    'followRedirects': false
  }
  response = UrlFetchApp.fetch('ログインページのURL', options)
  
  // レスポンスヘッダーから新たなCookieを取得(Set-Cookieが2つあるためgetAllHeadersで両方取得)
  cookies = response.getAllHeaders()["Set-Cookie"];
  cookies = cookies.toString()
  // Cookieからcookie_MoodleSessionを取得
  cookie_MoodleSession = CookieUtil.getValue(cookies, 'MoodleSession')
  
  //リダイレクト先を開く(GET)
  headers = {
    'cookie': 'MoodleSession=' + cookie_MoodleSession + ';',
    'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36'
  }
  options = {
    'method': 'get',
    'headers': headers,
    'followRedirects': false
  }
  
  //授業用ページを開く(GET)
  response = UrlFetchApp.fetch('授業ページのURL', options)
  
  //デバッグ用(ログインが成功しているか、授業ページのタイトルを確認)
  /*content = response.getContentText("UTF-8")
  
  $ = cheerio.load(content)
  
  let page_title = $("title").text()
  console.log(page_title)*/
}
```

初回のトリガーは自分で設定する必要があるので、
https://script.google.com/home/triggers
で設定しておきましょう。![トリガーの編集](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/784893/1e4a1ea0-7ee4-3823-b6a6-e234f7776640.png)

# 終わりに

以上がGASを活用した出席自動化です。このスクリプトを授業の数だけ複製すれば授業フル出席の優等生になれます。