Google Formでクイズして、自動で答え合わせして、メールまで送っちゃうお 

この記事はGoogle Apps Script Advent Calendar 2013の3日目の記事です。
昨日は...とか書こうと思ったら、Qittaの機能で出ているっぽいですね。

今日はGoogle Apps Scriptを使って、
「Google Formでクイズして、自動で答え合わせして、メールまで送っちゃうお 」
をやりたいと思います。

なお、この昨日はちゃんとライブラリ化してあるので、
ある程度簡単に使うことができるはずです。

# 導入 Google Form De Quiz

Google Appsを使っている企業や、個人的なコミュニティとかで、
Google Formでクイズを作成して、答えてもらうみたいなことはよくあると思います。

ただ答え合わせがめんどくさい。
人数が多くなればなるほど、答え合わせが億劫になってきます。

私が今働いている会社でも研修用のQuizがあって、
Google Formで作っているのですが、答え合わせがいつもめんどくさかったのです。

ということで作ってみました。

# どうなる？

実際にやってみたほうが早いですね。下のQuizに解答してみてください。
解答が終わると点数とともに答えが送られてくるはずです。（送られてこなかったら問い合わせしてください（白目
なお、メールアドレスを書いて頂いていますが、
Google AppsのFormの場合は自動でメールアドレスが収集できるので、
この入力欄は不要です。

https://docs.google.com/forms/d/1z8_8v872RQG3kFZF2KTQPwEWvVzqPDFK4sz5bfM60FE/viewform

## どうやる?

ざっくり言うと

* Quizつくって
* GASを設定して
* 1回100点の答えを出す

だけです。

なお今回はライブラリを使って説明していますが、
一番最後にライブラリのフルコードも乗っけるので、
カスタマイズが必要な場合は自分で直しちゃってください。

### 1. Quizを作ります。

普通にGoogle Formで作ってください。  
基本どの回答方式でも動くはずですが、
問題文中に「フィードバック」という言葉が含まれる場合は問題としてカウントされません。
問題に対するフィードバックとして設問を設置する場合のみ利用してください。

### 2. GASの設定

#### 2.1. エディタの起動

Google Formの設定画面より「ツール」→「スクリプトエディタ」を選択し、
GASのエディタを起動します。
一度Ctrl+S等を押して、GASプロジェクト名を任意の名前で保存してください。

![スクリーンショット 2013-12-02 21.03.36.png](https://qiita-image-store.s3.amazonaws.com/0/2965/62271755-cb6a-056b-224c-05db89f4a3c6.png)


#### 2.2. ライブラリの読み込み

GASのエディタより「リソース」→「ライブラリの管理」を選択し、ライブラリ管理画面を開きます。
開いた後、「ライブラリを検索」欄に`MYOunXg1tirujBAKMGAAOqwV62A3znfOO`を設定し、「選択」ボタンを押下

「`ValidateAnswer`」というライブラリが取り込まれたらバージョンを3に設定して、「保存」ボタンを押下

![スクリーンショット 2013-12-02 21.40.24.png](https://qiita-image-store.s3.amazonaws.com/0/2965/a8eec442-0cfb-85a5-2dc9-a4a748c567ab.png)


#### 2.3. コードの作成

開いていたGASのエディタに以下のコードを記述します。

```javascript
function onFormSubmit(e) {
  ValidateAnswer.onFormSubmit(e);
}
```

なお解答返信用メールアドレスは通常「返信用メールアドレス」という文言が、
アンケートの設問タイトルに含まれていると、
それの解答を返信用メールアドレスとして使う設定になっていますが
独自で設定したい場合は、以下のように設定します。

```javascript
function onFormSubmit(e) {
  ValidateAnswer.onFormSubmit(e, {mailAddressTitle : "あなたのメールアドレスを入力してください。"});
}
```
としてください。
そうすると、mailAddressTitleに設定した文字列と同じ設問の解答がメールアドレスとして
利用されます。

#### 2.4. トリガーの設定

Formへ回答時に自動的にスクリプトが動くように設定を行います。
GASのエディタより「リソース」→「現在のプロジェクトのトリガー」を選択

トリガー設定画面が開いたら
「トリガーが設定されていません。今すぐ追加するにはここをクリックしてください。」をクリックし、
以下のように設定

![スクリーンショット 2013-12-02 21.14.42.png](https://qiita-image-store.s3.amazonaws.com/0/2965/be50b3b0-9173-7b12-afbb-5c13d1836e63.png)

設定したら、保存ボタンを押します。

※この時、認可が問われますのでOKしてください。

### 3. 答えの作成

一度作成したQuizに満点になるように答えます。
解答後満点のメールが飛ばされれば設定完了です。

# 終わり

ということで、今回は自動回答スクリプトを作ってみました。
例えばこれに毎回選択式解答の順番を変えるなどのスクリプトを入れるとかなり、
それっぽいクイズが作れると思います。 :)

# フルコード

今回作ったライブラリのフルコードです。
なお内部でGasMarkedというMarkdownパーサも使ってます。
GasMarkedは[marked](https://github.com/chjj/marked)というライブラリのGAS用ラッパーライブラリです。

以下のライブラリーを取り込めば使えます。
`MmsnSQg-c3TgRNhenj8A7ReMffa6w-w2J`

また、スクリプト自体の解説が望まれるようであれば、また記事を書きたいと思います。

```js:Code.gs

/**
 * 答え合わせを行い、結果を返却します。<br/>
 *
 * 結果は以下の様な形になります。
 * <pre><code>
 * var quiz = {
 *   title : "Formのタイトル",
 *   questionCount : "設問の数（メールアドレス収集用や、フィードバック用の設問は除く）",
 *   score : "得点",
 *   answerer : "解答者のメールアドレス",
 *   publishedUrl : "Formの解答URL",
 *   results : [
 *     {
 *        question : {
 *          no : "Form上の設問位置",
 *          title : "設問のタイトル(多分問題文)",
 *          helpText : "設問の補助文",
 *          qNo : "設問番号"
 *        },
 *        answer : {
 *          correct : "true or false 正解したか",
 *          multi : "true or false複数選択式解答か",
 *          your : "解答 複数選択式回答の場合配列で設定",
 *          expected : "模範解答 複数選択式回答の場合配列で設定"
 *        }
 *     }
 *   ]
 * } 
 * </code></pre>
 *
 * @param {FormApp.Form} form 対象のフォーム
 * @param {FormApp.FormResponse} response 答え合わせを行う解答
 * @param {Object} option オプション <code>option.mailAddressTitle</code>を指定すると、そのタイトルを持つ設問の解答を解答者のメールアドレスとして利用します。
 * @return 答え合わせの結果
 */
function validateAnswer(form, response, option) {
//  var form = FormApp.getActiveForm();
//  var response = form.getResponses()[0];
  option = option || {};
  
  //模範解答
  var suggestedAnswers = form.getResponses()[0];

  var items = form.getItems();
  var results = [];
  var questionNo = 0;
  var answerEmail = null;
  
  //すべての設問でループ
  for(var i = 0; i < items.length; i++) {
    var item = items[i];
    
    //解答欄がないタイプのものはスキップ
    switch(item.getType()) {
      case FormApp.ItemType.SECTION_HEADER:
      case FormApp.ItemType.IMAGE:
      case FormApp.ItemType.PAGE_BREAK:
        continue;
    }
    
    var title = item.getTitle();
    
    //フィードバック用の質問もスキップ
    if(title.indexOf("フィードバック") >= 0) {
      continue;
    }
    
    //メールアドレス収集用のものは、メールアドレスを取得して次へ
    if(title.indexOf("返信メールアドレス") >= 0
       || title == option.mailAddressTitle) {
      answerEmail = response.getResponseForItem(item).getResponse();
      continue;
    }
    
    //結果を作成
    var result = {};
    
    result.question = {
      "no" : item.getIndex(),
      "title": item.getTitle(),
      "helpText" : item.getHelpText(),
      "qNo" : ++questionNo
    };
    
    //解答
    var answer = response.getResponseForItem(item).getResponse();
    
    //模範解答
    var expected = suggestedAnswers.getResponseForItem(item).getResponse();
    
    //複数選択式の場合は内容が合致しているか文字列にしてから検査
    if(Array.isArray(answer)) {
      result.answer = {
        correct : answer.sort().toString() == expected.sort().toString(),
        multi : true,
        your :  answer,
        expected : expected
      };
    } else {
      //文字列の場合はそのまま当てる
      result.answer = {
        correct : answer == expected,
        multi : false,
        your :  answer,
        expected : expected
      };
    }
    
    results.push(result)
  }  
  
  //全体の設定
  var quiz  = {};
  
  quiz.title = form.getTitle();
  quiz.questionCount = questionNo;
  quiz.score = results.filter(function(result){ return result.answer.correct}).length;
  quiz.answerer = answerEmail || response.getRespondentEmail();
  quiz.publishedUrl = form.getPublishedUrl();
  quiz.results = results;
  
  return quiz;
}


function onFormSubmit(e, option) {
  //var e.response = FormApp.getActiveForm().createResponse();
  option = option || {};
  var form = FormApp.getActiveForm();
  
  var quiz = validateAnswer(form, e.response, option);
  
  var temp = HtmlService.createTemplateFromFile("mailTemplate");
  
  temp.quiz = quiz;
  MailApp.sendEmail(
    quiz.answerer,
    "[採点結果]" + quiz.title,
    null,
    {
      noReply: true,
      bcc : option.bcc,
      htmlBody : GasMarked.create({gfm: true,breaks:true})(temp.evaluate().getContent())
    }
  );
}

```

```html:mailTemplate.html
To <?=quiz.answerer?> さん


「<?=quiz.title?>」へのご解答有難うございました。

あなたの得点は、、、、

<font size="20" color="red"><?=quiz.score?></font>点 / <?=quiz.questionCount?>点 でした

再挑戦する場合は、以下からお願い致します。

<?=quiz.publishedUrl?>


※詳細な結果は下の方に書いています。
<?for(var i=0;i < 50;i++){?>
↓  
<?}?>



## 答え合わせ

<?
for(var i = 0; i < quiz.results.length; i++){
  var q = quiz.results[i];
  if(!q.answer) continue;
?>

======================

問 <?=q.question.qNo?>   
<?=q.question.title?> 

> <?=q.question.helpText?> 

結果：<?=q.answer.correct ? "○" : "×"?> 

<?if(q.answer.multi) {?>
あなたの答え : 
<?=q.answer.your.join(",  \r\n")?> 

正解 : 
<?=q.answer.expected.join(",  \r\n")?> 
<?}else{?>
あなたの答え : <?=q.answer.your?> 

正解 : <?=q.answer.expected?> 


<?}?>

<?}?>



```