Googleクラウドサービスを利用して時間指定メール送信システムを作る（その８）

***
＜[前へ](https://qiita.com/matsuhandy/items/a481260a9fd9bc1254fc)＞　＜[次へ](https://qiita.com/matsuhandy/items/ddb7ed092b29c5b8b3c5)＞
　メール送信順のため，フォーム入力内容が蓄積されたスプレッドシートから必要データを切り出し，別のスプレッドシートとして，mailフォルダに格納する作業を行います。
***
#1. 送信準備のためフォームに入力されたデータをmailフォルダに保存

![画像2.png](https://qiita-image-store.s3.amazonaws.com/0/146927/2f6cfb09-a62a-7d1f-496d-a926050d2320.png)

* 時間指定メール送信フォームが完成し，入力した内容を修正用URLを入力した人に返信できるようになった。フォームに入力した内容は一つのスプレッドシートに蓄積されるので，送信準備のため，入力した内容を切り出して，mailフォルダに格納する。ソフトウェア構成図の④，⑤，⑥を行う。

1. フォームで送信が行われた段階で，二重起動を防ぐためのロック等の処理をした後，フォームで作成されたSpreadsheetの最下行のデータを取得する。
1. 取得したデータを専用フォルダ「mail」内に用意した新規Spreadsheetに書き込む。
1. 一定時間（たとえば１分）ごとに実行するGASプログラムで，現在日時を取得し，保存されたメールファイルの中から，送信日時が現在時刻より古いものを抽出し送信する。
1. 送信したファイルは削除する。

　今日は，2までを行う。

#2.フォーム入力データが格納されたGoogle Spreadsheetの作成
* 時間指定メール送信システムフォームに，データを5件程度入力せよ。過去と未来の両方の送信日時を含むものとする。送信メール内容は適当で構わないが，メールの宛先は必ず自分の公式メールアドレスにすること。
* フォーム編集画面から「回答」をクリック。

![画像38.png](https://qiita-image-store.s3.amazonaws.com/0/146927/40ac2733-0037-0d83-d140-154f93189ef4.png)

* スプレッドシートの作成，をクリック。新しいスプレッドシートを作成する。

![画像39.png](https://qiita-image-store.s3.amazonaws.com/0/146927/b5438724-64a8-c3ee-c137-b104afdb46fd.png)

---

![画像40.png](https://qiita-image-store.s3.amazonaws.com/0/146927/0a812a9f-bb9d-279b-d81e-151f99fe76c0.png)


* 新しいSpreadsheetが作成され，自動で開かれる。

![画像41.png](https://qiita-image-store.s3.amazonaws.com/0/146927/c88d129c-27fb-57eb-19ca-2bd35a9331c8.png)

* Google Driveを見るとSpreadsheetが確認できる。

![画像42.png](https://qiita-image-store.s3.amazonaws.com/0/146927/ee652b4c-85e9-b2d7-18a2-c6a92fbc53e4.png)

* 新規にフォルダ「mail」を作成する。

![画像47.png](https://qiita-image-store.s3.amazonaws.com/0/146927/8a7d958c-7377-c35b-3480-474a33c2ec2f.png)

* **SpreadsheetとmailフォルダのIDを確認しておく。**mailフォルダのIDは，そのフォルダに入ってURLで確認する。folders/の後ろの部分がID。
* Spreadsheetのツールメニューからスクリプトエディタを起動し，プロジェクト名を変更。

![画像43.png](https://qiita-image-store.s3.amazonaws.com/0/146927/86bc935a-c977-4d13-e4c0-09fa2d5ba318.png)

---

![画像44.png](https://qiita-image-store.s3.amazonaws.com/0/146927/3f5ec523-cd40-4043-589a-aa141217afed.png)

---

![画像45.png](https://qiita-image-store.s3.amazonaws.com/0/146927/f314485c-5fd8-1cea-c400-60e92e986c92.png)

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

![画像19.png](https://qiita-image-store.s3.amazonaws.com/0/146927/70934c8a-6037-0562-122b-64aa7c6325d6.png)

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

![100.png](https://qiita-image-store.s3.amazonaws.com/0/146927/8877ef7d-0626-97f6-4c1e-596398f63fa1.png)

---
* log2に実行ログを書き込んだり，エラーを書き込んだりするので，開発中はlog2を開いたままにしておく。
* log2のファイルIDをコピーし，あとで使用するため，エディターに貼り付けておく

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

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

```java:テンプレート
var logID    = "**********";//手動でDocument，「log2」を新規作成しIDを調べておく
var ssID1    = "**********";//フォーム入力で作成されたSpreadsheetのID
var ssID2    = "";                                            //mailフォルダに作成されたSpreadsheetのID
var folderID = "**********";                //mailフォルダのID

function submitForm(){//フォームが送信されたら呼び出される関数，重複処理を避ける
  log = new Doc(logID);
  log.print('\n'+getDateAndTime(0)+" スクリプト開始\n");
  var lock = LockService.getScriptLock();//ロックサービスのオブジェクトを生成
  try{
    lock.waitLock(30000);//複数のフォーム送信がほぼ同時にあった時，遅い方に最大30秒待ってもらう
    log.print("他のスクリプト実行要求をロック完了，最大60秒\n");
    main(log);
  }catch(err){
    log.print("発生したエラー："+err+'\n');
  }finally{
    lock.releaseLock();//次の送信のためにロック解除
    log.print(getDateAndTime(0)+" ロック解除，次のスクリプト要求を受け付け開始\n");
  } 
}

function main(log){
  var ssSrc = new Ssheet(ssID1);//フォーム入力されたデータが入っているSpreadsheet
  var todayNow =getDateAndTime(0);//現在日時の取得

  var ssID2 = createSpreadsheetInFolder(folderID, "temp");//新規作成，ファイル名はとりあえずtemp，後で送信日時に変更する
  var ssDst = new Ssheet(ssID2); //最後にフォーム入力されたデータだけを記録するSpreadsheet

  var maxRow = ssSrc.getLastRow(0);
  var maxColumn = ssSrc.getLastColumn(0);
  log.print("最後の行"+maxRow+" 最後の列"+maxColumn+"\n");

  for(var i=1;i<=maxColumn;i++){
    var index = ssSrc.getValue(0, 1, i);
    ssDst.setValue(0, 1, i, index);
    var data = ssSrc.getValue(0, maxRow, i);
    if(index == "date"){
      var dateTmp = data;
      var date = getDateAndTime(dateTmp).split('_')[0];
      log.print(date+"\n");
      ssDst.setValue(0, 2, i, date);    
    }
    if(index == "time"){
      var timeTmp = data;
      var time = getDateAndTime(timeTmp).split('_')[1];
      log.print(time+"\n");
      ssDst.setValue(0, 2, i, time);
    }else{
      ssDst.setValue(0, 2, i, data);
    }
  }
  var fn = date + "_" + time;
  ssDst.rename(fn);
  log.print("最後に入力されたデータを新規Spreadsheetにコピー終了，ファイル名を送信日時に変更\n");

}

//-----------------------日付や時間のための関数群
function getSerial(date,time){//日付と時間からシリアル値をゲット
  var serial = new Date(date.toString().slice(0,16)+time.toString().slice(16));
  return serial;
}

function getDateAndTime(data){// 引数がゼロなら現在日時そうでなければ指定日時をyyyymmdd_hhmmssで返す
  if(data==0) var now = new Date();
  else        var now = new Date(data);
  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).slice(0,4) + ("0"+month).slice(-2) + ("0"+day).slice(-2) +'_'+ 
    ("0"+hour).slice(-2) + ("0"+min).slice(-2) + ("0"+sec).slice(-2);
}

//-----------------------ファイルの新規作成等の関数群
//フォルダID，ファイルID，ファイル名を受け取り，ファイルIDのコピーをフォルダ内に作成して，ファイルのIDを返す
function copyFileInFolder(folderID, srcID, fileName) {
  var originalFile = DriveApp.getFileById(srcID);
  var folder = DriveApp.getFolderById(folderID);
  var copiedFile = originalFile.makeCopy(fileName, folder);
  copiedFile.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.EDIT);//リンクからアクセスできる人は編集可能にする
  var copiedFileId = copiedFile.getId();//コピーのファイルIDをゲット
  return copiedFileId;
}
//フォルダID，ファイル名を受け取り，スプレッドシートを指定フォルダ内に新規作成しそのファイルIDを返す
function createSpreadsheetInFolder(folderID, fileName) {
  var folder = DriveApp.getFolderById(folderID);
  var newSS=SpreadsheetApp.create(fileName);
  var originalFile=DriveApp.getFileById(newSS.getId());
  var copiedFile = originalFile.makeCopy(fileName, folder);
  DriveApp.getRootFolder().removeFile(originalFile);
  var copiedFileId = copiedFile.getId();//コピーのファイルIDをゲット
  return copiedFileId;
}

//////////Ssheetクラスの定義開始（コンストラクタとメンバ関数で構成）
//Ssheetクラスのコンストラクタの記述
Ssheet = function(id){
  this.ssFile = SpreadsheetApp.openById(id);
  this.ssFileName = this.ssFile.getName();
  SpreadsheetApp.setActiveSpreadsheet(this.ssFile);//値を返さない
  this.activeSpreadsheet = SpreadsheetApp.getActiveSpreadsheet();
}
//Ssheetクラスのメンバ関数（メソッド）の定義開始
//spreadsheetのファイル名を返すメソッド
Ssheet.prototype.getFileName = function(){
  return this.ssFileName;
}
//spreadsheetのファイル名を変更するメソッド
Ssheet.prototype.rename = function(newName){
  this.ssFile.rename(newName);
}
//spreadsheetのsheetでrow行cal列にデータを入れるメソッド
Ssheet.prototype.setValue = function(sheet,row,col,value){
  this.activeSheet = this.activeSpreadsheet.getSheets()[sheet];
  var cell = this.activeSheet.getRange(row,col);
  cell.setValue(value);
}
//spreadsheetのsheetでrow行cal列をクリアするメソッド
Ssheet.prototype.clear = function(sheet,row,col,value){
  this.activeSheet = this.activeSpreadsheet.getSheets()[sheet];
  var cell = this.activeSheet.getRange(row,col);
  cell.clear(value);
}
//spreadsheetのsheetからrow行のcal列のデータをもらってくるメソッド
Ssheet.prototype.getValue = function(sheet,row,col) {
  this.activeSheet = this.activeSpreadsheet.getSheets()[sheet];
  var value = this.activeSheet.getRange(row, col).getValue();
  return value;
}
//背景の色を設定するメソッド
Ssheet.prototype.setBackgroundColor = function(sheet,row,col, r,g,b) {
  this.activeSheet = this.activeSpreadsheet.getSheets()[sheet];
  var cell = this.activeSheet.getRange(row,col);
  cell.setBackgroundRGB(r,g,b);
}
//spreadsheetのsheet数を指定の数増やすメソッド
Ssheet.prototype.insertSheet = function(num){
  var sheetNum = this.activeSpreadsheet.getNumSheets();
  while(num>sheetNum){
    this.activeSpreadsheet.insertSheet();
    sheetNum++;
  }
}
//spreadsheetの指定sheetを削除
Ssheet.prototype.deleteSheet = function(sheet){
  this.activeSpreadsheet.deleteSheet(this.activeSpreadsheet.getSheets()[sheet])
}
//spreadsheetのsheetの名前をセットするメソッド
Ssheet.prototype.renameSheet = function(sheet,newName){
  this.activeSheet = this.activeSpreadsheet.getSheets()[sheet];
  this.activeSheet.setName(newName);
}
//spreadsheetの指定sheetの全データを取得
Ssheet.prototype.getValues = function(sheet){
  this.activeSheet = this.activeSpreadsheet.getSheets()[sheet];
  return this.activeSheet.getDataRange().getValues();//シートの全データを取得
}  
//spreadsheetの指定sheetを取得
Ssheet.prototype.getSheet = function(sheet){
  this.activeSheet = this.activeSpreadsheet.getSheets()[sheet];
  return this.activeSheet;//シートを返す
}
//spreadsheetの指定sheetの最後の行番号を取得
Ssheet.prototype.getLastRow = function(sheet){
  this.activeSheet = this.activeSpreadsheet.getSheets()[sheet];
  return this.activeSheet.getLastRow();//最後の行番号を取得
}
//spreadsheetの指定sheetの最後の列番号を取得
Ssheet.prototype.getLastColumn = function(sheet){
  this.activeSheet = this.activeSpreadsheet.getSheets()[sheet];
  return this.activeSheet.getLastColumn();//最後の列番号を取得
}
//spreadsheetの指定行rowを削除
Ssheet.prototype.deleteRow = function(sheet,row){
  this.activeSheet = this.activeSpreadsheet.getSheets()[sheet];
  this.activeSheet.deleteRow(row);//行を削除
}
//spreadsheetの指定行rowを挿入
Ssheet.prototype.insertRow = function(sheet,row){
  this.activeSheet = this.activeSpreadsheet.getSheets()[sheet];
  this.activeSheet.insertRows(row);//行を挿入
}
//spreadsheetの指定列colを削除
Ssheet.prototype.deleteColumn = function(sheet,col){
  this.activeSheet = this.activeSpreadsheet.getSheets()[sheet];
  this.activeSheet.deleteColumn(col);//列を削除
}
//spreadsheetの指定列colを挿入
Ssheet.prototype.insertColumn = function(sheet,col){
  this.activeSheet = this.activeSpreadsheet.getSheets()[sheet];
  this.activeSheet.insertColumns(col);//列を挿入
}
//////////Ssheetクラスの定義終了

//////////Docクラスの定義開始（コンストラクタとメンバ関数で構成）
//Docクラスのコンストラクタの記述
Doc = function(id){
  this.ID = id;
  this.doc = DocumentApp.openById(this.ID); 
  this.body = this.doc.getBody();
  this.docText = this.body.editAsText();
}
//Docクラスのメンバ関数の定義開始
//メソッドprintの定義，テキスト追加
Doc.prototype.print = function(str){
  this.docText.appendText(str);
}
//メソッドreplaceの定義，文字列置き換え
Doc.prototype.replace = function(src,dst){
  this.body.replaceText(src,dst);
}
//メソッドclearの定義，全消去
Doc.prototype.clear = function(){
  this.body.clear();
}
//メソッドgetIDの定義，ファイルIDを返す
Doc.prototype.getID = function(){
  return this.ID;
}
//指定秒数のウェイト，表示動作を遅らせたい時などに使用
Doc.prototype.waitSec = function(sec){
  var start = new Date().getSeconds();
  while((new Date().getSeconds()-start) < sec);
}
//指定ミリ秒のウェイト，表示動作を遅らせたい時などに使用
Doc.prototype.waitMiliSec = function(msec){
  var start = new Date(); //new Date()は，「1970年1月1日午前0時」からの通算ミリ秒を返す
  while((new Date()-start) < msec);
}
//今現在の日時を表示
Doc.prototype.printTodayNow = function(){
  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();
  this.docText.appendText(year +'_'+ ("0"+month).slice(-2) +'_'+ ("0"+day).slice(-2) +' '+ 
    ("0"+hour).slice(-2) +'-'+ ("0"+min).slice(-2) +'-'+ ("0"+sec).slice(-2));
}
/////////Docクラスの定義終了
```

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

![画像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)

---

![画像33.png](https://qiita-image-store.s3.amazonaws.com/0/146927/916bb2ba-1fc2-a22e-6c36-e95d7bdf5567.png)

---
* log2を見えるようにした状態で，フォームに入力して送信する。間違っても人に迷惑をかけないため，送信先アドレスは自分のアドレスにする。
* log2で動作チェックを行う。フォーム入力で作成されたSpreadsheetの最下行に追加されたデータが，mailフォルダに新規作成され，送信日時のファイル名となっているSpreadsheetの内容になっていることを確認する。


***
　次は，タイムベース実行する関数を作成し，1分ごとに実行し，メール送信時間になったら，メール送信を行います。
＜[前へ](https://qiita.com/matsuhandy/items/a481260a9fd9bc1254fc)＞　＜[次へ](https://qiita.com/matsuhandy/private/bfa0fe32c669b6474692)＞
***