Google Formに入力された内容をメールで返すGoogleAppsScriptプログラム（ロックサービスによる多重投稿対応）

# 重複処理を避けてGoogle Form入力データ確認メールの返信

　Google Formでアンケートを作って，その内容を入力した人にメールで返したい。また返した内容を後から変更できるように，変更するURLもメール中に書いておきたい。さらに同時にアンケートに入力があっても，問題なく順番に処理されるようにGoogle Apps Script（以下，GAS）を記述したい。
　これらのテンプレートを公開。少しラフですがご容赦。C言語ができる人向けの解説も追加。

## C言語とGASの違い（簡単に）
* 変数は型推定が行われるのですべて，varとして宣言する。
* プリミティブ（原始的）な型は，newを使ってオブジェクトを生成する必要はない。具体的には整数・実数・文字・文字列など。
* 代入するもので型が決まったり型が自動変換されるなどする。

```java:変数の宣言例
var num = 0;
var str = "abc";
for(var i=0; i<10; i++){
  ･･･
}
```

* 文字列・文字を表現するのに，ダブルクォーテーション，シングルクォーテーションのどちらで囲っても良い。一文字の時シングル，文字列の場合ダブル，というように決めておくと良い。
* 配列の場合は，変数名に「s」を付けるというルールが一般的。
* 変数名・関数名は小文字で始めて単語の切り替わりは大文字にするルールが一般的。たとえば，googleAppsScriptという感じに大文字を入れる。
* GAS（等の言語では）変数名はインスタンスやオブジェクトと呼ばれる。
* プリミティブ（原始的）でないクラスについては，new演算子を使ってオブジェクトを生成してあげる必要がある。次の例ではDateクラスのオブジェクトを作っている。
* 文字列などプリミティブ（原始的）な型もメンバ関数を持つ。次の例のsplitやtoStringなど。

```java:変数名の付け方
var now = new Date();  //現在時間を取得，シリアル値で得られる
var str = now.toString(); //シリアル値を文字列に変換（例：'Sat Oct 29 2016 16:05:42 GMT+0900 (JST)'）
var strings = str.split(' '); //半角スペースで切り分けて配列に入れる
return strings[4]; //時間を返す
```

* SpreadsheetやDocument等はすでにあるオブジェクトなので，それを扱うのにnewはしない。新規作成する場合もnewでなく，専用の関数がある。
* 変数のスコープはCと同じと考えてほぼ間違いない。
* リマーク分は，//，や，/**/が使える。
* ファイルは固有のIDで管理される。従って同じファイル名も存在できる。ややこしいので注意する。
* ファイルIDはファイルをブラウザ上で開いて，URLで確認する。
* フォルダIDも同様に，そのフォルダを開いて，URLで確認する。
* 関数はすべて，次のように定義。returnは必要に応じて書く。返すものがなければ書かなくてよい。型は自動判定されるので関数に型指定は不要。可変引数（ここでは取り扱わない）も可能。

```java:関数の定義の方法
function funcName(argument1, argument2, ･･･){
  ･･･
  return ･･･;
}
```
* Cの構造体をGASではクラスと呼ぶ。クラスにはメンバ変数の他，メンバ関数がある。それらはインスタンスに「.」でつなげて指定する。例えば次の`now.toString()` や`str.split(' ')`が例である。メンバ関数に何があるかはネットで検索するしかない。
* メンバ関数は，メソッドと呼ばれる。
* GASのマニュアル：https://developers.google.com/apps-script/

```java:インスタンスのメンバ関数の呼び出し方
var now = new Date();  //現在時間を取得，シリアル値で得られる
var str = now.toString(); //シリアル値を文字列に変換（例：'Sat Oct 29 2016 16:05:42 GMT+0900 (JST)'）
var strings = str.split(' '); //半角スペースで切り分けて配列に入れる
return strings[4]; //時間を返す
```
* 文字列の連結は，単に「+」でつないでいけばよい。strcatの様な関数は使わない。連結するデータが入った変数が数値でもうまく勝手に変換してくれる。itoaの様な関数は使わない。
* この本を読めばいい，という王道はない。ひたすらネット検索でメソッド等の情報を集めたり，誰かが書いたサンプルプログラムを研究するしかない。

## フォームの作成

* とりあえずアンケート等のフォームを作成する。（さすがに簡単なので解説は省略）
* メールアドレスは，メールアドレスの書式以外は受け付けないように設定するとよい。

![03.png](https://qiita-image-store.s3.amazonaws.com/0/146927/567e6724-cbf0-88cf-8582-d62319419ec7.png)


## デバッグのための準備

* デバッグやエラーを出力するlogファイルを作成。新規，から，Googleドキュメント，を選択し，「log」というドキュメントを作成する。

![07.png](https://qiita-image-store.s3.amazonaws.com/0/146927/bfea5b96-4cd6-8c21-3672-0802f7715b19.png)

* 名前を「log」に変更する。勝手に保存される。

![画像20.png](https://qiita-image-store.s3.amazonaws.com/0/146927/e7a2c8f0-f53e-ba14-9fb1-5e57d194ad84.png)

---

![08.png](https://qiita-image-store.s3.amazonaws.com/0/146927/9f996214-174d-c61d-abea-9b5f27ecb796.png)

* logに実行ログを書き込んだり，エラーを書き込んだりするので，開発中はlogを開いたままにしておくとよい。
* GASプログラムはフロッピーマークをクリックして自分で保存する。保存し忘れても次回起動時に保存されてないものを復活させるかどうか聞かれるので慌てる必要はない。
* logのファイルIDをコピーし，あとで使用するため，エディタ等に貼り付けておく。

![画像22.png](https://qiita-image-store.s3.amazonaws.com/0/146927/6685a154-4da1-1449-a894-0aa50bc34085.png)

## Google Formに紐付けされたGAS

* Google Formからスクリプトエディタを起動し，名前を変更する。

![01.png](https://qiita-image-store.s3.amazonaws.com/0/146927/0c7b0906-941a-40b1-4937-510b2ad4be44.png)

---

![02.png](https://qiita-image-store.s3.amazonaws.com/0/146927/dee64bb9-249c-40c4-821d-def54b665951.png)


* 最初から書かれているGASプログラム，「コード.gs」の4行は全削除する。
* 次の内容をコピーペーストする。「**********」の部分は，エディタに貼り付けたIDに置き換える。

```java:テンプレート
function submitForm(e){//フォームが送信されたら呼び出される，重複処理を避ける
  var log = DocumentApp.openById("**********");//事前にドキュメントを作りIDをしらべておく
  printDoc(log,'\n'+getDateAndTimeOfNow()+" スクリプト開始\n");
  var lock = LockService.getScriptLock();//ロックサービスのオブジェクトを生成
  try{
    lock.waitLock(30000);//複数のフォーム送信がほぼ同時にあった時，遅い方に最大30秒待ってもらう
    printDoc(log,"他のスクリプト実行要求をロック完了，最大30秒\n");
    main(e,log);
  }catch(err){
    printDoc(log,"発生したエラー："+err+'\n');
  }finally{
    lock.releaseLock();//次の送信のためにロック解除
    printDoc(log,getDateAndTimeOfNow()+" リリースロック，次のスクリプト要求を受け付け開始\n");
  } 
}

function main(e, log){
  printDoc(log,"main関数開始\n");
  var itemResponses = e.response.getItemResponses(); 
  var message = '';

  printDoc(log,"入力内容取得開始\n");  
  for (var i = 0; i < itemResponses.length; i++) { 
    var itemResponse = itemResponses[i]; 
    var question = itemResponse.getItem().getTitle(); 
    var answer = itemResponse.getResponse();
    if(answer=='') answer="未入力";
    message += question + "\n->" + answer + '\n';
    if(question == "氏名")           var name = answer;
    if(question == "メールアドレス") var mail = answer;
  }
  printDoc(log,"入力内容取得終了\n");  

  var body = name + " 様\n" 
    +"入力を受け付けました。\n"
    +"このメールは自動返信メールです。\n"
    +"入力内容は次の通りです。\n"
    +"入力内容は次のURLから編集できます。\n"
    
  var editURL = e.response.getEditResponseUrl(); //回答編集用URLを取得
  printDoc(log,"編集用URLを取得\n");  

  body += (editURL + "\n\n" + message);
  
  printDoc(log,"入力内容確認メール送信開始\n");
  MailApp.sendEmail({// メール送信
    to: mail,
    cc: "",
    subject: "出欠アンケート",
    name: "アンケートシステム",
    body: body
  });
  printDoc(log,"メール送信終了\n");
  printDoc(log,"main関数終了\n");  
}


//-----------------------日付や時間のための関数群

function getDateAndTimeOfNow(){// 現在日時を文字列で返す
  var now = new Date();
  var year = now.getYear();
  var month = now.getMonth() + 1;
  var day = now.getDate();
  var hour = now.getHours();
  var min = now.getMinutes();
  var sec = now.getSeconds();
  return year +'_'+ ("0"+month).slice(-2) +'_'+ ("0"+day).slice(-2) +' '+ 
    ("0"+hour).slice(-2) +'-'+ ("0"+min).slice(-2) +'-'+ ("0"+sec).slice(-2);
}


//-----------------------デバッグ用の関数群
//docの中身を全部削除
function docClear(doc){
  doc.getBody().clear();//中身をクリア
  //    doc.saveAndClose();
}

function printDoc(doc,str){//docにstrを書き込む
  var docText = doc.getBody().editAsText();
  docText.appendText(str);
  //    doc.saveAndClose();
}

```

* 現在のプロジェクトのトリガーを設定する。

![画像28.png](https://qiita-image-store.s3.amazonaws.com/0/146927/2ff02742-b788-d773-fdec-1677e6fd64cc.png)

---

![画像29.png](https://qiita-image-store.s3.amazonaws.com/0/146927/daf67cb7-a058-187e-177f-5e66b2f43a41.png)

---

![画像30.png](https://qiita-image-store.s3.amazonaws.com/0/146927/8c13d10f-bf90-cdf1-6df5-12a087b125e4.png)

* 承認の許可を求められるので，内容を確認して許可する。

![画像31.png](https://qiita-image-store.s3.amazonaws.com/0/146927/f7f9b613-834b-0896-3565-95b8e96cc4f6.png)

---

![04.png](https://qiita-image-store.s3.amazonaws.com/0/146927/91c2aca3-ad7b-16ec-c132-31bd68005fda.png)

* logを開いて見えるようにした状態で，アンケートフォームに入力して送信する。なお，今後，GASの実行前にlogファイルについて「Ctrl+A Delete」で内容を全部削除しておくと見やすい。
* logに記録される内容を確認する。実行ログとエラーが表示（エラーが発生した場合）されているはずである。

![05.png](https://qiita-image-store.s3.amazonaws.com/0/146927/fa682d9f-6b3f-da42-9b8b-8a94ecda498a.png)

* 次のようなメールが届けば正解。同時にアンケートに入力があっても後から来た方を待たせてすべて処理を受け付ける。

![06.png](https://qiita-image-store.s3.amazonaws.com/0/146927/d422e83e-2e84-eed1-c2de-d828139df36e.png)