この記事は、GAS(Google Apps Script)とDiscordを連携させた英単語テスト自動作成システムの開発記録・第6回です。
前回の記事では、saiten 関数の実装①について説明しました。
今回は saiten 関数②として、実装完了までやっていきます。
saiten 関数作成②
前回、回答処理関数processResponsesをsaiten関数の中に作ったところまで終わった。よって、今回は分岐処理から最後のトリガー設定までをやっていく。
分岐処理
フォームの説明文でテストの種類を判定し、分岐処理をする。
let totalQuestions;
if (formDescription !== "苦手全問テストだよ!") {
totalQuestions = QUESTION_COUNT;
} else {
totalQuestions = form.getItems().length;
}
processResponses(responses, totalQuestions);
- 苦手問題全問テストの場合は問題数を数えて
totalQuestionsを設定する -
processResponses関数を実行
Discordで結果を送信
採点結果をDiscordとスプレッドシートに通知する。
//送信内容を入れる変数
let message = [];
// 結果表示メッセージ作成
message.push(`${index}問中 ${correctCount}問正解`);
//正答率によって異なる応援メッセージを選択
if (correctCount === index) message.push("\nおめでとう!全問正解だ!");
else if (correctCount === 0) message.push("\n明日は頑張ろう\n\n<間違えた問題>");
else if (index - 1 === correctCount) message.push("\n惜しい!もう少しで全問正解だよ!\n\n<間違えた問題>");
else if (incorrectCount >= index / 2) message.push("\nまずまずだね\n\n<間違えた問題>");
else message.push("\n上出来だね\n\n<間違えた問題>");
//間違えた問題の詳細を追加
message = message.concat(incorrectDetails);
// スプレッドシートに結果を書き込む
urlSheet.getRange(2, 2, 1, 1).setValue(message.join(""));
// Discordに送信
const payload = { content: message.join("") };
const options = { method: "post", payload: payload };
UrlFetchApp.fetch(WEBHOOK_URL, options);
コード解説
1.結果表示メッセージ
message.push(`${index}問中 ${correctCount}問正解`);
-
indexはprocessResponses関数実行後なので全問題数になっている
${変数名}
- 今までのコードでも数回使っているかもしれないが、これで変数を文字列に埋め込むことができる
-
+を使ってもよい
2.応援メッセージ&間違えた問題の詳細追加
//正答率によって異なる応援メッセージを選択
if (correctCount === index) message.push("\nおめでとう!全問正解だ!");
else if (correctCount === 0) message.push("\n明日は頑張ろう\n\n<間違えた問題>");
else if (index - 1 === correctCount) message.push("\n惜しい!もう少しで全問正解だよ!\n\n<間違えた問題>");
else if (incorrectCount >= index / 2) message.push("\nまずまずだね\n\n<間違えた問題>");
else message.push("\n上出来だね\n\n<間違えた問題>");
//間違えた問題の詳細を追加
message = message.concat(incorrectDetails);
- 正解数がどれくらいかでメッセージを変える
- 全問正解の時以外は改行して末尾に
"<間違えた問題>"をつけ、誤答の見出しとする -
.concat()で後ろにまとめて追加している
3.スプレ・Discordに結果通知
// スプレッドシートに結果を書き込む
urlSheet.getRange(2, 2, 1, 1).setValue(message.join(""));
// Discordに送信
const payload = { content: message.join("") };
const options = { method: "post", payload: payload };
UrlFetchApp.fetch(WEBHOOK_URL, options);
- スプレッドシートの「フォームのリンクと結果」シートに結果を入力する
-
message.join("")によって配列を文字列に変換(各要素の先頭や末尾に \n(改行文字)を入れているため、結果的にDiscordやスプレッドシートでは改行されて表示される) - Webhookを利用してDiscordに送信
フォーム削除処理
処理の都合上フォームを作成すると以下のようなシートもスプレッドシートに作成されてしまう。

毎回フォームや連携されたシートが作成されると、Googleドライブの容量を圧迫するなどの不利益が生じるため、採点が終わったらフォームを削除するようにする。
// フォームと連携したスプレッドシート削除処理
for (const sheet of allSheets) {
if (sheet.getFormUrl()) {
const linkedForm = FormApp.openByUrl(sheet.getFormUrl());
const linkedFormId = linkedForm.getId();
linkedForm.removeDestination();
ss.deleteSheet(sheet);
const file = DriveApp.getFileById(linkedFormId);
file.setTrashed(true);
}
}
コード解説
1.フォームと連携しているシートを探す
if (sheet.getFormUrl()) {
}
- シートがフォームと連携していない場合は、
sheet.getFormUrl()がnullを返すため処理は実行されない
2.フォームを削除できる状態にする
const linkedForm = FormApp.openByUrl(sheet.getFormUrl());
const linkedFormId = linkedForm.getId();
-
sheet.getFormUrl()でシートの連携元のフォームIDを取得し、openByUrl()で操作可能なフォームオブジェクトにする - 操作可能なフォームオブジェクトのIDも取得(IDを用いて削除するため)
3.フォームとシートを削除
linkedForm.removeDestination();
ss.deleteSheet(sheet);
const file = DriveApp.getFileById(linkedFormId);
file.setTrashed(true);
- 先に
.removeDestination()でフォームとスプレの連携を切る(ここで連携を切っておかないと後で参照エラーが発生する可能性がある) - スプレッドシートを削除する
- Googleドライブ上にあるフォームをフォームIDから取得し、削除する
ここまででsaiten関数本体の実装は完了した。試しにmake_testを実行してテストに回答した後saitenを実行してみると、Discordとスプレッドシートは以下のようになる。
また、スプレッドシートに作成されたシートや回答済みのフォームも問題なく削除されている。
トリガー設定
フォームが送信された際にsaiten関数が自動的に実行されるようにしなければいけないので、トリガーを設定していく。まず、GASの画面左側から「トリガー」というメニューを選択する。
「トリガーを追加」を押すと以下のような画面になるので、実行する関数をsaiten、イベントの種類を「フォーム送信時」に変更する。
※この時、トリガーを「フォーム送信時」に実行させるため、作成したフォームをスプレに連携させていた。
以上の設定を行えば、フォームに回答すると自動でsaiten関数が実行されるようになる。
また、ここでmake_test関数もトリガーに設定すれば、指定した時間に自動でテストが作成されるようになる。このときの設定は、イベントのソースを「時間主導型」にし、時間ベースのトリガーのタイプを「日付ベースのタイマー」、時刻を好きな時刻にすれば良い。
おわりに
今回はsaiten関数実装完了までと、トリガーについての説明をしました。
そして、ここまでで基本的な機能の実装は終了しました。
よって、次回からは苦手問題全問テスト機能の実装に取りかかっていきます。






