3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

時間指定メール送信の準備・Google Spreadsheetでメールを送る準備(J3-04)

Last updated at Posted at 2017-05-08

1. 入力内容をGoogle Spreadsheetで操作

 フォームに入力した日時指定送信メールの内容をGoogle Spreadsheetで操作する。時間指定メール送信システムを構築するにあたり,いろいろアルゴリズムは考えられるが,メール送信プログラムはタイマーによりフォーム入力とは非同期で動作させることにする。
 フォームで入力されたSpreadsheetを非同期で開いて各行にあるデータの送信時間を確認する方法では,徐々にデータが溜まってしまい処理が遅くなっていくことが予想される。そこで,フォームに入力され次第,メール送信用の別Spreadsheetを特定のフォルダ(例えばmail)内に作成し,そのファイル名を送信日時にしておく。
 非同期で起動したプログラムで,そのフォルダ内にファイルがあるファイルを確認し,送信日時に現在日時が到達していれば,送信作業を行う。送信後はそのファイルは削除する。
 フォーム入力があると,そのSpreadsheetにはフォームデータ送信と同時に最下行に付け加えられていく仕様となっている。よって,そのSpreadsheetに紐付けられたGASプログラムで,mailフォルダに作ったSpreadsheetにフォームで作成されたSpreadsheetの最下行だけをコピーしておき,メール送信に備えておく。

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

 今日は2までを行う。下の図では⑤,⑥を実行するプログラムを書く。図には「最下行だけを1行目にコピー」となっているが,間違いである。「インデックスを1行目,最下行を2行目にコピー」が正しいので,注意する。課題の説明の通りにとくこt

画像3.png

1.1. フォーム入力データが格納されたGoogle Spreadsheetの作成

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

画像38.png

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

画像39.png


画像40.png

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

画像41.png

  • Googleのバグ?
    ファイルSpreadsheetの,ファイル,設定,をクリックし,言語と地域が,日本,Tokyo,になっているか確認せよ。なっていない場合は修正せよ。(タイムゾーンが,Delhi,になっている場合がある。どうもGASを開発している拠点のタイムゾーンになっている可能性が高い。2022年は問題なかったので,Googleの開発においてバグが発生したものと思われる)
    image.png

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

画像42.png

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

画像47.png

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

画像43.png


画像44.png


画像45.png

  • 最初から書かれているGASプログラム,「コード.gs」の4行は全削除する。
  • 次の内容をコピーペーストする。「**********」の部分は,それぞれ適切なIDに置き換える。logファイルはGoogle Formと供用しない。
  • 課題の実行テストの方法は,プログラムリストの下に示す。

//ここから,mailフォルダに作成したSpreadsheetの1行目に送信するプログラムを書き,そのSpreadsheetのファイル名を
//getDateAndTimeOfNowShort()と同じ形式に名前を変更する。

<課題8>
 テンプレートを元に次のプログラムを完成させよ。全てプログラムで実行すること。

  • フォームにより作成されたスプレッドシートの1行目(インデックス)をmailフォルダに新規作成されたSpreadsheetの1行目にコピーする。
  • フォームにより作成されたスプレッドシートの最下行のデータをmailフォルダに新規作成したSpreadsheetの2行目にコピーする。
  • 新規作成したSpreadsheetのファイル名をフォーム入力された送信予定日時にする。そのフォーマットはgetDateAndTimeOfNowShort()と同じとする。
  • フォーム入力して「mailフォルダに作成された送信日時がファイル名となっているSpreadsheet」を提出フォルダにアップロードせよ。
テンプレート
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'+getDateAndTimeOfNowShort()+" スクリプト開始\n");
  var lock = LockService.getScriptLock();//ロックサービスのオブジェクトを生成
  try{
    lock.waitLock(30000);//複数のフォーム送信がほぼ同時にあった時,遅い方に最大30秒待ってもらう
    log.print("他のスクリプト実行要求をロック完了,最大30秒\n");
    main(log);
  }catch(err){
    log.print("発生したエラー:"+err+'\n');
  }finally{
    lock.releaseLock();//次の送信のためにロック解除
    log.print(getDateAndTimeOfNowShort()+" ロック解除,次のスクリプト要求を受け付け開始\n");
  } 
}

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

  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");
  //ここから先を完成させる

  

    
  

  
}


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

function getDateAndTimeOfNow(){// 現在日時を文字列でゲット
  var now = new Date();
  var year = now.getFullYear();//getYear()+1900
  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);
}

function getDateAndTimeOfNowShort(){// 現在日時を文字列でゲット
  var now = new Date();
  var year = now.getFullYear();//getYear()+1900
  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);
}

function getTime(serialValue){//シリアル値から時間を取り出す,シリアル値は「1899年12月30日午前0時」からの経過ミリ秒のはず
  var str = serialValue.toString();//(例:'Sat Oct 29 2016 16:05:42 GMT+0900 (JST)')
  var strings = str.split(' ');
  return strings[4];
}

function getDate(serialValue){//シリアル値から年月日を取り出す
  var str = serialValue.toString();//(例:'Sat Oct 29 2016 16:05:42 GMT+0900 (JST)')
  var strings = str.split(' ');
  return strings[0] +' '+ strings[1] +' '+ strings[2] +' '+ strings[3];
}



//-----------------------ファイルの新規作成等の関数群
//フォルダ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);//2020/10にサポート終了した様子
  originalFile.setTrashed(true);
  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クラスの定義終了

  • 実行テストは次のように行う。submitFormを実行テストするのではないことに注意。今回はフォーム入力直後のプログラムの動作を確認したいので,mainをテスト実行する。

画像49.png

3
4
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?