2023年 1月 の投稿一覧

AIチャットボットにGASを書いてもらったら実用レベルで絶望した件

0.はじめに

0-0.ごあいさつ

こんにちは!
今回はちょっとおふざけ回「AIにコード書かせてみた」です。
実際に実務で書いてきたようなコードを、AIに書いてもらいました。

「AIによってプログラマは不要に」なんて説も聞こえてくる昨今ですが
果たして人気AIサービスの実力や如何に。

0-1.使用するAIチャットボットについて

2023年1月現在、話題沸騰のChatGPTを使っていきます。
ChatGPT公式
「平気で嘘ついてくる」等の評価もありますが、
はたして日常業務アプリのコーディングの場合はどうでしょうか。

1.Googleフォーム自動回答

最初の設問はこちら。

googleフォーム回答があった際に自動的にメールを送信するGASコードを書いてください。

 

chatGPT選手の回答コードはこちら!

function sendEmail(e) {
  var email = "youremail@example.com"; // Change to your email address
  var subject = "New Response to Google Form";
  var body = "A new response was submitted to the Google Form.\n\n";
  for (var i = 0; i < e.values.length; i++) {
    body += e.namedValues[Object.keys(e.namedValues)[i]].toString() + "\n";
  }
  MailApp.sendEmail(email, subject, body);
}
/**chatGPTがこのコードを書いてくれた後に付属していた補足コメント This code uses the sendEmail method from the MailApp class to send an email with a specified subject and body. The body of the email contains all the responses submitted in the Google Form. To use this code, create a new Google Apps Script project, paste the code into the script editor, and save the project. Then, go to the Google Form, click on the "Responses" tab, and select "Get pre-filled URL." This will open the form with sample data filled in. Click the "Submit" button to trigger the function and send an email.*/

いかがでしょうか。
パッと見、よく書けているように思いますが
私の環境ではエラーを吐きましたw
e.valuesが不正、とのことですが本論から逸れるので省きます。

constやletを使用しないところや、
for文の中身が少し冗長なのが気になりますが

動かなかったことは別として、要望通りの方向性でコードを書けていることは分かります。

ただ、当然のことですが
必ずしも動くものが返ってくるわけではない
ということは念頭に置いておく必要がありそうですね。

2.スプレッドシート更新時通知

次の設問はこちら。

スプレッドシートの最終行にデータが入った際に自動で
その入力された内容をメール送信するGASを書いてください

 

chatGPT選手の回答コードはこちら!

function sendInvoiceEmail() {
    // Get the active spreadsheet and the last row
    var sheet = SpreadsheetApp.getActiveSheet();
    var lastRow = sheet.getLastRow();
    
    // Get the values of the last row in an array
    var rowValues = sheet.getRange(lastRow, 1, 1, sheet.getLastColumn()).getValues()[0];
    
    // Assign the values to variables for use in the email
    var date = rowValues[0];
    var invoiceNum = rowValues[1];
    var clientName = rowValues[2];
    var description = rowValues[3];
    var quantity = rowValues[4];
    var rate = rowValues[5];
    var amount = rowValues[6];
    
    // Create the email subject and body
    var subject = "Invoice #" + invoiceNum + " for " + clientName;
    var body = "Dear " + clientName + ",<br><br>" + 
               "Please find attached the invoice for " + description + " with the following details:<br>" + 
               "<b>Date:</b> " + date + "<br>" + 
               "<b>Invoice #:</b> " + invoiceNum + "<br>" + 
               "<b>Description:</b> " + description + "<br>" + 
               "<b>Quantity:</b> " + quantity + "<br>" + 
               "<b>Rate:</b> " + rate + "<br>" + 
               "<b>Amount:</b> " + amount + "<br><br>" + 
               "Thank you for your business!<br><br>" + 
               "Best regards,<br>Your Name";
    
    // Send the email
    MailApp.sendEmail(clientEmail, subject, body, {htmlBody: body});
    }
    /**
     * chatGPTがコードを書いた後に残してくれた補足
     Note: This example assumes that you have a column with the client's email address
           in the spreadsheet, and that you have set the variable clientEmail
           to the appropriate email address in the script.

           You can run this script by selecting Run > sendInvoiceEmail in the
           Google Apps Script editor, or by setting a trigger to run the script
           automatically whenever a new row is added to the spreadsheet.
     */

はい。いかがでしょうか。
実はこの設問の前に、興味本位で別の質問をしておりまして、
「スプレッドシートを使って請求書のフォーマットを作成してください。」
というやり取りがあったからか、前提が請求系SSになっちゃってます。

このコードも一応使えそうですね。
特に指定していないのにbタグで「日付」「ナンバー」などを太字にするなど、
見やすさにも配慮が届いたコードになっています。

ありがたい気遣いですね。

3. 1000件ある社内のGoogleグループを全件管理するシート作って!

3-1.質問側に問題があるパターン

次の設問はこちら。

所属するドメインのGoogleグループのメールアドレスとエイリアスと名称を全件取得して
スプレッドシートに貼り付けるGASを書いてください。
グループの総数は約1000件あり、増加しても問題ないコードにしてください。

 

chatGPT選手の回答コードはこちら!

function getGroupInfo() {
    // Get the active spreadsheet and the last row
    var sheet = SpreadsheetApp.getActiveSheet();
    
    // Clear the existing data in the spreadsheet
    sheet.clearContents();
    
    // Get all the Google Groups in the domain
    var groups = GroupsApp.getGroups();
    
    // Create an array to store the group information
    var groupData = [];
    
    // Loop through each group and get the group email, alias, and name
    for (var i = 0; i < groups.length; i++) {
      var group = groups[i];
      var email = group.getEmail();
      var alias = group.getAliases().join(', ');
      var name = group.getName();
      
      // Add the group information to the array
      groupData.push([email, alias, name]);
    }
    
    // Paste the group information into the spreadsheet
    sheet.getRange(1, 1, groupData.length, groupData[0].length).setValues(groupData);
  }
/** chatGPTがコードの後に残してくれた補足
You can run this script by selecting Run > getGroupInfo in the Google Apps Script editor.
This script will retrieve all the Google Groups in the domain and
paste their email addresses, aliases, and names into the active Google Sheets spreadsheet,
starting from the first row.
*/  

意図と異なる回答が返ってきてしまいましたね。
自身が入っていないものも含め、社内のグループ全件を対象としたかったのですが、
このコードでは、自身のアカウントだけが所属しているGoogleグループ一覧を作成しています。
ただ、「日本語という言語の壁が大きい」という話もよく耳にします。
ということで、質問の仕方を変えてみました。

3-2.質問の仕方を工夫してみた

設問はこちら。

AdminDirectoryを使用して社内のGoogleグループのメールアドレスとエイリアスと名称を
全件取得してスプレッドシートに貼り付けるGASを書いてください。
グループの総数は約1000件あり、増加しても問題ないコードにしてください。

 

chatGPT選手の回答コードはこちら!

function getGroupInfo() {
    // Get the active spreadsheet and the last row
    var sheet = SpreadsheetApp.getActiveSheet();
    
    // Clear the existing data in the spreadsheet
    sheet.clearContents();
    
    // Get the domain for which you want to retrieve the groups
    var domain = 'your_domain.com';
    
    // Create a new AdminDirectory API service
    var service = AdminDirectory.Groups.list({domain: domain});
    
    // Get all the Google Groups in the domain
    var groups = [];
    do {
      var response = service.execute();
      groups = groups.concat(response.groups);
      service.maxResults = response.nextPageToken;
    } while (response.nextPageToken);
    
    // Create an array to store the group information
    var groupData = [];
    
    // Loop through each group and get the group email, alias, and name
    for (var i = 0; i < groups.length; i++) {
      var group = groups[i];
      var email = group.email;
      var alias = group.aliases.join(', ');
      var name = group.name;
      
      // Add the group information to the array
      groupData.push([email, alias, name]);
    }
    
    // Paste the group information into the spreadsheet
    sheet.getRange(1, 1, groupData.length, groupData[0].length).setValues(groupData);
  }
/**
ChatGPTがコードの後に残してくれた補足
You can run this script by selecting Run > getGroupInfo in the Google Apps Script editor.
This script will retrieve all the Google Groups in the domain and
paste their email addresses, aliases, and names into the active Google Sheets spreadsheet,
starting from the first row. Note that you need to replace 'your_domain.com'
with your actual domain name. Additionally, you need to grant the script
the necessary permissions to access the AdminDirectory API by going to
File > Project properties > Add-ons and enabling the Google Admin SDK API.
*/  

いかがでしょうか。これは素晴らしいのではないでしょうか。
検証してませんが、多分動くと思います。
最初にclearContentsが入っているあたり、
このスクリプトでデータを何度も上書きすることをちゃんと想定していますね。

4.pivotGroupのSortBy()の使い方を教えて!

設問はこちら。
GASでシート上ピボットテーブルの並び順を変えたい時に質問したものです。

GASのpivotgroupクラスに対するsortBy(value, oppositeGroupValues)メソッドを使ってカスタムソートする方法が分かりません。
どうやって使うか具体例を出して解説してください。

 

chatGPT選手の回答はこちら!

//GASのpivotgroupクラスのsortBy()メソッドを使用すると、ピボットテーブルの特定の値に基づいてグループを並べ替えることができます。
//sortBy()メソッドには、次の2つの引数が必要です。

//value - グループを並べ替えるために使用する値の列の見出し(必須)
//oppositeGroupValues - value列で並べ替えたい値のリスト(オプション)
//以下は、具体的な例として、pivotgroupクラスのsortBy()メソッドを使用してピボットテーブルのグループを並べ替える方法を示します。

//例として、以下のような売上データのピボットテーブルがあるとします。
//	A	B	C
//1	商品	地域	売上合計
//2	商品A	東京	1000
//3	商品A	大阪	2000
//4	商品B	東京	3000
//5	商品B	大阪	4000
//このテーブルを、地域別に売上合計で並べ替えたい場合を考えます。この場合、sortBy()メソッドを使用して、売上合計列を並べ替えることができます。
//以下のスクリプトは、上記のピボットテーブルを読み込み、地域別に売上合計で並べ替えます。
function customSort() {
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1');
  var pivotTable = sheet.getPivotTables()[0];
  var group = pivotTable.getRowGroup(1); // 地域のグループ
  var sortByColumn = pivotTable.getValues()[0].indexOf('売上合計') + 1; // 売上合計の列の番号
  group.sortBy(sortByColumn);
}

//上記の例では、sortBy()メソージを使用して、value引数に渡す列の見出しを売上合計として取得し、
//oppositeGroupValues引数を省略して、デフォルトの昇順ソートを実行しています。

このコード、確実に動きません!

なぜならpivotGroupクラス.getValues()なんてメソッドは存在しないからです。

ということで、設問を追加してみます。

GASにpivotGroup.getValues()っていうメソッド、ないよね?

 

対してchatGPT。


//はい、申し訳ありません。
//先程の回答において、getValues()というメソッドが存在するかのように誤った情報を提供してしまいました。
//実際には、PivotGroupクラスにはgetValues()メソッドは存在しません。
//正しい情報を提供するために、修正版の回答を再度提供いたします。

function customSort() {
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1');
  var pivotTable = sheet.getPivotTables()[0];
  var group = pivotTable.getRowGroup(1); // 地域のグループ
  var dataRange = pivotTable.getDataRange();
  var values = dataRange.getValues();
  var sortByColumn = values[0].indexOf('売上合計') + 1; // 売上合計の列の番号
  var oppositeGroupValues = ['大阪', '東京']; // 逆順で並べ替えたい地域
  group.sortBy(sortByColumn, oppositeGroupValues);
}

//この修正版の回答では、PivotTableクラスにはgetValues()メソッドは存在しないことを正確に反映しています。
//代わりに、getDataRange()メソッドを使用して、ピボットテーブルのデータ範囲を取得し、
//getValues()メソッドを使用して、そのデータ範囲のすべての値を取得することができます。
//お手数をおかけして申し訳ありませんでした。

うん、pivotGroup.getDataRange()も存在しねーから!

「嘘ばっかやんけ!」と言いたくなりますが、更に聞いてみます。

pivotGroupクラスにはgetDataRangeってメソッドも無いよね。

続いてchatGPT。


//申し訳ありません、私の前回の回答に誤りがありました。
//PivotGroupクラスには、getDataRange()というメソッドは存在しません。
//正しい情報を提供するために、修正版の回答を提供いたします。
function customSort() {
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1');
  var pivotTable = sheet.getPivotTables()[0];
  var group = pivotTable.getRowGroup(1); // 地域のグループ
  var pivotValues = group.getValues(); // グループの値を取得
  var sortByColumn = pivotTable.getValues()[0].indexOf('売上合計') + 1; // 売上合計の列の番号
  var oppositeGroupValues = ['大阪', '東京']; // 逆順で並べ替えたい地域

//~~~~まだ回答は続いてますが以下略。

ということで、最初に「無いよ」と指摘した「getValues」を使う嘘回答にループしました。

実務が絡んでいたので、この後も根気良く工夫して質問しました。
色々な出口を潰す質問方法だったり、実際のテーブル構造をどうにか伝えたり。
結果、chatGPTを用いての解決には至りませんでした。
自分で工夫してピボットテーブルの構造を変更してソート用の数値データ列を持ち昇順ソートで解決しました。

ちなみにsortByメソッドでのカスタムソート方法は公式以外の情報が落ちておらず、
GPTなら、と思いましたが、謎の存在しないメソッドを提案してくる始末でした。

5.まとめ

初めての検証企画でしたが、いかがでしたでしょうか。

実は最後の設問、私がGASを学び始めてifとforしか分からない時に
苦労して悩みながら何とか解決したテーマなんです。思い出深い。
(ifとforだけで頑張ってる方はこちらの記事も見てみてください。)

最後の事例では、上手く使わないとチャットのやり取りに時間を奪われるだけでした。
chatGPTに対して「それ間違ってる!」と訴え続ける時間は何とも価値の低い時間でした。
また、質問している視点に回答も自分の思考も縛られてくるので、
「あれ、そもそもカスタムソートしなくても解決できるやん?」
という風な別視点での問題解決への気付きを失いやすいなと感じました。
人間の思考停止への道がここにあるなと感じさせられました。

まだまだな部分もありますが、コード間のコメントなど配慮がきいている部分も多々あり、
「苦労して書いたコードが数秒で・・・」と絶望する方もおられるかもしれませんね。
私としては「適切性のために不合理を選択する」等の点で人間捨てたもんじゃないと思いますが、
皆さんはいかがでしょうか。

もしよろしければコメントで頂けると嬉しいです。ありがとうございました!

フォームのデータを取得できませんでした。のエラーに対処した話

2024/3/12 追記

強引に発生確率を下げる方法について、2024年の記事で解説しています。
両記事ともある程度有効な手段かと思いますので、あわせてご参照ください。
フォームのデータを取得できませんでした。に対処したもう1つのシンプルな方法

0.はじめに

2022年12月頃から、Googleフォームの送信時トリガーで
以下のようなエラーが発生し不安定な状態となっているようです。
フォームのデータを取得できませんでした。のエラー画像
私の環境でも発生していたため、
一助になれればと思い対応した方法を記載しておきます。
※エラーそのものを発生させなくする方法ではありません。

 

1.何が起こったのか

フォーム送信時トリガーを設定した時、
送信時作成のイベントから中身を取り出そうとする段階でエラーを吐きます。

エラーメッセージ:Exception: フォームのデータを取得できませんでした。しばらくしてからもう一度お試しください。

ただ毎回起こるわけではなく、不定期に発生するのが厄介です。

2.根本解決の方法はあるのか

調査したところ、根本的に当エラーが発生しなくする方法は見つけられませんでした。
なので一旦「エラーが起きても問題ない形」をご提案します。

 

 

3.即時リトライを試みる方法

3-1.コード

//フォーム送信時トリガーを設定
function formRes(e) {
  try{  //通常処理
    const itemresponses = e.response.getItemResponses(); //ここで対象のエラーが発生する 
    const address = e.response.getRespondentEmail();
    main(itemresponses, address);
  }catch(e){
    //エラー時
    console.log(e);

    //対象のフォームを取得 
    const form = FormApp.getActiveForm();
    //対象フォームの回答を過去分から全て取得する
    const allItemresponses = form.getResponses();
    //最新分は配列末尾に入っているため最新分を取得 
    /** 精度重視であれば、allItemresponsesの中身をgetTimestamp()して日次比較、最新分を取得する方がベターでしょう。*/ 
    const recentResponse = allItemresponses[allItemresponses.length-1]; 
    
    //recentResponseは、tryの中のe.responseと似た扱いができる
    const itemresponses = recentResponse.getItemResponses(); 
    const address = recentResponse.getRespondentEmail();
    main(itemresponses, address); 
  }
}               
                 
function main(itemresponses, address){
  //フォーム回答を使って行いたい処理 
  //メール送信するなりチャットツールに通知するなりデータ加工して二次利用するなり
  console.log("main"); 
}

 

3-2.解説

エラーが起きるところをtryに入れ、該当のエラー発生時にcatchします。
catchしたら無理に送信時トリガーでのフォーム情報を使わず、
formオブジェクト.getResponses()で対象フォームの回答全件を取得します。
そこから最新の回答を絞り込み、本来処理に持ち込む形です。

 

3-3.デメリット

3-3-1.ほぼ同時に複数の回答があった場合の精度が保証できない

リトライ処理中に他の回答があった場合、
最新側を取得して処理に入ってしまう可能性があります。

 

3-3-2.即時でない

当然、本来のフォーム送信時トリガーよりは反応が遅くなってしまいます。

 

3-3-3.リトライ処理自体も失敗することがある

このコードをテストしている時、リトライ処理自体も1回エラーを吐きました。
Exception: Failed to retrieve form data. Please wait and try again.
失敗した直後のcatchでリトライ処理を試みると、
結構な確率でFormにアクセスする段階でエラーを吐く印象です。
「リトライ処理自体は失敗しても5回まで繰り返す」等の対策があった方が良さそうです。

 

3-4.類似の方法

似ている方法として、フォーム回答を蓄積するスプレッドシートにGASを仕込み、
シート更新に応じて最新データを取得、処理する方法があります。

デメリットとしては、フォームの設問内容を追加/削除した際、
過去の質問項目もシートに残るため、正確に情報を抜き出すことが難しそうです。
「回答があったよ!」等の情報だけが必要であれば、これで良いかもしれませんね。

4.日次で処理漏れを検知、再実行する方法

4.1コード

//フォーム送信時トリガーを設定
function formRes(e) {
  try{
    //通常処理
    const itemresponses = e.response.getItemResponses();
    const address = e.response.getRespondentEmail();
    main(itemresponses, address);

    //ログシートを取得できれば方法は何でもいい
    const logSheet = SpreadsheetApp.openById("SSのID").getSheetByName("フォーム後処理ログ");
    const lastRow = logSheet.getLastRow();
    const responseId = e.response.getId();
    const timeStamp = e.response.getTimestamp();

    //ログ書き込み
    logSheet.getRange(lastRow+1,1,1,2).setValues([timeStamp, responseId]);

  }catch(e){
    //失敗時 一応タイムリーにエラーキャッチしたいならメール飛ばす等を実装    
  }
}

function main(itemresponses, address){
  //フォーム回答を使って行いたい処理 メール送信するなりチャットツールに通知するなりデータ加工して二次利用するなり
  console.log("main");
}

//昨日分の履行をチェックする 日次トリガーにする
function dailyCheck(){
  //昨日のdayjsオブジェクト
  const yesterday = dayjs.dayjs().subtract(1,"day");

  //昨日の回答を全件取得する
  //ライブラリdayjsを使用しているが、timestampが昨日であるかを確認できれば方法は何でもいい
  const form = FormApp.openById("フォームID");
  const yesterdayResponses = form.getResponses()
                            .filter(x => dayjs.dayjs(x.getTimestamp()).isSame(yesterday,"day"));
  //昨日のフォーム回答が0件だったらここで処理終了
  if(yesterdayResponses.length==0) return;

  //ログシートからログを全件取得し、空白を除外、A列に入力されているtimestampが昨日のものを抽出
  const logSheet = SpreadsheetApp.openById("シートID").getSheetByName("フォーム後処理ログ");
  const logs = logSheet.getRange("A1:A").getValues().filter(x => x)
                .filter(x => dayjs.dayjs(x[0]).isSame(yesterday,"day"));
  
  //ログシートの情報と、フォームから直接取得した昨日の回答全件を照らし合わせ、
  //ログシートに記載のないものを抽出する=送信時にtryが失敗していたものを抽出
  const targets = yesterdayResponses.filter(x => !logs.find(y => y[1]==x.response.getId())); 
  if(targets.length == 0) return;

  //メイン処理を実行する
  for(let target of targets){
    let itemresponses = target.getItemResponses();
    let address = target.getRespondentEmail();
    main(itemresponses, address);
  }
}

 

4-2.解説

仕組み自体が少々まどろっこしいかもしれません。ゆっくり読んでください。

 

4-2-1.実行ログを取る

まず、本来の送信時トリガーでの処理時に必ず実行ログを取るようにします。
発動時に取得するイベントオブジェクト(e)から、
「回答ID」を取得してログシートに入力する仕組みです。
※シートはどこでもいいですが、回答記録されるSS上にシートを作っておくと管理上楽でしょう。

 

4-2-2.ログシートの内容と、フォーム回答全件を比較する

さて、そうすると、当該エラーを吐いた時には「回答ID」はログには残りません。
この状況を利用します。

毎日0時~1時に、対象フォームの昨日分回答を全件取得して、
その内容とログシートの内容が一致していれば、
エラーは発生していなかったという事になります。
逆に一致していなければ、ログ記録未実行=失敗しているものがあります。

コードと一緒に見ていきましょう。

 

4-2-3.実行が漏れていた分の処理を実行する

エラーで処理が実行されていなかった分は、本来処理を改めて実行しましょう。
実行するための回答情報は、前項で取得した未実行分の「回答ID」を使って取得します。
コードですと、この部分になります。
フォーム送信時トリガーと扱いが同じオブジェクトを取得できちゃいます。

4-3.メリット・デメリット

4-3-1.【メリット】即時リトライよりは精度が上がる

ほぼ同時に複数の回答があった場合の、回答の取り違えが発生しません。
これは項3で紹介した方法より良い部分ですね。

 

4-3-2.【デメリット】回答からのタイムラグが大きい

今回のケースですと、分かりやすく日次トリガーを設定して
前日分の動作に問題が無かったかを検知しますので、遅いです。
午前9時にエラーを吐いた場合、その回答への対応は翌日になります。

フォームの用途上、タイムラグが許されない場合は
数時間毎に検知できるように工夫してみたり、
エラー時にメール等で通知し日中帯は即時フォローできるようにするなど、
工夫をすればある程度カバーできる問題かと思います。

 

4-3-3.【デメリット】日次処理自体が失敗する可能性がある

項3の方法で触れましたが、こちらでも似たような処理を実行しているため
何度か実行を試みるような処理を追加した方が無難だと思います。

 

4-3-4.【デメリット】処理が煩雑になる

最後はやっぱりこれに尽きますね。地味に面倒くさい。
「修正してくれ~」と思ってしまいますね。
ですが、GASやSaaS系ツールはサーバー側の問題で不安定になりますから、
それも見越して厚めのエラー処理を当たり前に仕込むべきなのかもしれませんね。

 

5.さいごに

いかがでしたでしょうか。
私はというと、2つ目の方法を採用して日次で管理しています。

ちなみに私の環境では送信時トリガーのフォームを20個ほど管理していますが、
昨年12月中旬からポツポツとエラーが発生し、2023/1/16以来発生していません。
皆さんはいかがでしょうか?よければコメントください。
⇒2023年2月現在、やはりたまに発生するようです。

もしかすると解消されたのかもしれませんが、
Google側が不安定になると同様の事が起こるかもしれません。
時間がある際に対処しておく事をおすすめします。

また、強引にエラーハンドリングを行う方法として他でも使えるかもしれません。
参考にしていただけると幸いです。

Googleフォームでメール自動返信!GASとHTMLのコード実例をご紹介

1.はじめに

「Googleフォーム回答に対してカスタマイズした形で自動返信したい!」
そんな方へ向けてGASでの解決方法を解説した記事になります。
ぜひ参考にしてみてください。

2.作成方法

2-0.フォームを用意する

業務本番で使っているフォームにいきなり仕込むのは危険です。
本番フォームをコピーしたり、テスト用に新設して用意してください。

2-1.スクリプトエディタを開く

自動回答を仕込みたいGoogleフォームの編集画面上で、
スクリプトエディタを開いてください。
この画面にコードを書き込んでいきます。
  フォーム右上のメニューからスクリプトエディタを開く

2-2.gsファイルとhtmlファイルをエディタに作成する

今回はGAS用ファイル1つとhtmlファイル1つを使用します。
画面左側からファイルをスクリプトファイルとhtmlファイルを作成してください。
エディタ内でスクリプトファイルとHTMLファイルを作成する

2-2.コードを書く

今回私が作成したコードはこちらです。
後ほど細かく解説していきます。

//GASコード

/**
 * メイン処理
 * @param {object} e    イベントオブジェクト 質問文や回答内容を取り出せる
 */
function sendFormResponse(e){
  //フォーム回答情報をeから取得しitemresponsesに格納
  const itemResponses = e.response.getItemResponses();

  //フォームタイトルを取得
  const formTitle = FormApp.getActiveForm().getTitle();

  //回答者アドレスを収集するフォーム設定の場合、回答者アドレスを取得できる
  //収集せず固定宛先に送る場合はaddress="~~~~~~@~~~.~~"とする
  const address = e.response.getRespondentEmail();
  
  //メール設定を取得する
  const mailSettings = getMailSettings(itemResponses, formTitle, address);

  //sendMail関数を発動 メール設定を渡す
  sendMail(mailSettings);
}

/**
 * メール設定を作成
 * @param {object} itemResponses  フォーム情報
 * @param {string} formTitle    フォーム名称
 * @param {string} address     回答者メールアドレス
 * @return {object} {body:本文html, subject:メールタイトル文字列, mailTo:メールToアドレス文字列} 
 */
function getMailSettings(itemResponses, formTitle, address){
  //回答内容を「質問文:[改行][スペース]回答内容[改行]質問:・・・」となるようhtml文字列を生成
  const qaHtml = itemResponses.map(res => 
         `${res.getItem().getTitle()}:<br> ${res.getResponse()}`)
                 .join("<br><br>");

  //メール.htmlの内容に、qaHtmlとformTitleの内容を適用
  const mailBody = HtmlService.createHtmlOutputFromFile("メール.html").getContent()
                   .replace("@replace_qa@",qaHtml)
                   .replace("@replace_formtitle@", formTitle);

  const subject = `${formTitle}の回答を受け付けました。`;
  const mailTo = address;
  return {body:mailBody, subject:subject, mailTo:mailTo};
}

/**
 * メール送信
 * @param {object} mailSettings {body:本文html, subject:メールタイトル文字列, mailTo:メールToアドレス文字列}
 */
function sendMail(mailSettings){
  GmailApp.sendEmail(mailSettings.mailTo,mailSettings.subject,"",{htmlBody:mailSettings.body});
}
フォーム「@replace_formtitle@」の回答を受け付けました。<br>
後のご対応については〇〇にて回答いたします。<br>
回答まで今しばらくお待ちください。<br>
<br>
@replace_qa@
<br>
※このメールは自動返信です。<br>
3営業日以内に連絡がない場合、お手数ですが下記へお問い合わせください。<br>
問合せ先:〇〇〇@〇〇〇〇.jp

※コード内にもかなり細かく解説コメントを書いています。
邪魔になる場合はお手数ですが削除してください。

2-3.認証を通しておく

スクリプトがフォーム回答等にアクセスするので、
それを予めOKかどうか許可しておく必要があります。
一度、スクリプト起動して許可しましょう。
権限を許可します

2-4.トリガー設定

この設定を行うと、フォーム回答時にスクリプトが発動するようになります。
これで、フォーム回答すると自動的に反応するようになります。

画面左のメニューから「トリガー」を選択、
「トリガーを追加」をクリックします。

トリガーを作成します

メイン処理をフォーム送信時起動の設定にして保存しましょう。
メイン処理をフォーム回答時起動に設定して保存

3.コード解説

3-1.sendFormResponse(e)

3-1-0.この関数について

Googleフォーム回答時に自動で呼び出される関数です。

3-1-1.eについて

イベントの略称でeとしています。
「フォーム回答」というイベントが発生すると、
フォーム回答に関する情報が発生し、自動的にeに格納されると思ってください。

3-1-2.const itemResponses = e.response.getItemResponses();

eの中から各質問ごとの詳細を取り出します。
itemResponses[0].getItem().getTitle()とすれば1問目の質問文を、
itemResponses[0].getResponse()とすれば1問目の回答を得られます。
公式ドキュメント

3-1-3.const formTitle = FormApp.getActiveForm().getTitle();

フォームタイトルを取得します。
getActiveとありますが、このスクリプトと紐づいているフォームを取得します。

3-1-4.const address = e.response.getRespondentEmail();

eの中から、回答者のメールアドレスを取得します。
フォーム設定で回答者アドレスを記録する設定の場合のみ、有効です。
公式ドキュメント

3-2.getMailSettings(itemResponses, formTitle, address)

3-2-0.この関数について

メール設定情報を作成するための関数です。

3-2-1.const qaHtml = itemResponses.map…

質問と回答を、綺麗な文字列になるように処理しています。
brタグはhtmlで改行を意味します。

3-2-2.HtmlService.createHtmlOutputFromFile(“メール.html”).getContent()

メール.htmlの内容をテキストとして取得します。

3-2-3.replace(“@replace_qa@”,qaHtml)

メール.htmlの@replace_qa@部分を、qaHtmlに置き換えます。

3-2-4.return {body:mailBody, subject:subject, mailTo:mailTo}

呼び出し元のsendFormResponse関数へ情報を返します。
返った情報はmailSettingsに格納されます。

3-3.sendMail(mailSettings)

3-3-0.この関数について

mailSettingsを用いてメールを送信します。

3-3-1.GmailApp.sendEmail();

フォームのオーナーアカウントからGmailでメールを送信します。
()の中に宛先やタイトル、本文情報など必要情報を入力します。
公式:

4.まとめ

いかがでしたでしょうか。 GoogleフォームとGASは使いこなせばかなり業務で役立ちます。
ぜひコードを書いてみて、活用してみてください。

また今回のコードは、全くの初心者の方には少し難しく、
玄人の方は「こうした方がいい」と思うコードだと思います。
ご自身なりに試行錯誤しながらアレンジしてみてください。

今後も、初心者の方がコードを理解しやすいような記事や、
改善事例、コード例などを発信していきますのでチェックしてみてください。