この記事は、GAS(Google Apps Script)とDiscordを連携させた英単語テスト自動作成システムの開発記録・第7回です。
前回の記事では、saiten 関数の実装②について説明し、基本機能の実装を終えました。
今回は、新しく「苦手問題全問テスト」機能の実装を始めていきます。
make_nigate関数の流れ
以下のような順序で処理を実行していく関数を作る。
- 「苦手問題」シートを一度空にする
- 苦手問題の定義を決めておき、それに適する問題を選び出す
- 苦手問題がなかったときに中断する
- フォームを作成
- 先ほど選んだ問題をフォームに設定する
- Discordに作成した英単語テストのURLを送信
make_nigate関数作成①
まず関数make_nigateを定義する。
function make_nigate() {
}
スプレッドシートの準備
「苦手問題」シートは未編集だったので、以下のように変更する。
このシートには、1つ前の苦手問題全問テストの回答データを記録する。
苦手問題のみ別途復習しやすくするため、このようにしている。
スプレッドシートなどの取得
make_nigate関数内で使うスプレ類を取得しておく。
//スプレッドシート取得
const ss = SpreadsheetApp.getActiveSpreadsheet();
//スプレッドシートのシート取得
const questionSheet = ss.getSheetByName("テストに出す英単語用");
const answerSheet = ss.getSheetByName("回答収集用");
const weakSheet = ss.getSheetByName("苦手問題");
const urlSheet = ss.getSheetByName("フォームのリンクと結果");
//「苦手問題」シートの最終列・行を取得
const weakLastRow = weakSheet.getLastRow();
const weakLastCol = weakSheet.getLastColumn();
//「回答収集用」シート1列目の最後の行を取得
const answerLastRow = answerSheet.getRange(answerSheet.getMaxRows(), 1).getNextDataCell(SpreadsheetApp.Direction.UP).getRow();
//「回答収集用」シート10列目の最後の行を取得
const lastRowJ = answerSheet.getRange(1, 10).getDataRegion(SpreadsheetApp.Dimension.ROWS).getLastRow();
- 「苦手問題」シートの最終列・行は、1行目以外のデータを削除する際に使用
- 「回答収集用」シートの10列目の最終行は、正答率を順に見ていく際に使用
「苦手問題」シートを初期化
//最初の行以外をクリア
if (weakLastRow > 1) {
weakSheet.getRange(2, 1, weakLastRow - 1, weakLastCol).clearContent();
}
前回のテストの回答が記録されているため、削除する。
苦手問題を抽出
苦手問題として出題する問題を選んでいく。今回は正答率が50%未満かつ出題回数が5回以上である問題を苦手問題に選んだ。苦手問題の基準を変えたいときは、If文の条件を変えれば良い。
//「回答収集用」シートの正答率などが記録されている範囲の情報を取得
const accuracyRange = answerSheet.getRange(2, 10, lastRowJ, 3);
const accuracyData = accuracyRange.getValues();
//苦手問題を入れておく配列
const weakQuestions = [];
//一個ずつ苦手問題かどうか見ていく
for (let i = 0; i < accuracyData.length; i++) {
//正答率
const accuracy = accuracyData[i][1];
//出題数
const count = accuracyData[i][2];
//正答率が50%未満かつ出題回数が5回以上のものを苦手問題に追加
if (accuracy < 0.5 && count >= 5) {
weakQuestions.push(questionSheet.getRange(i + 2, 1, 1, 5).getValues().flat());
}
}
コード補足
weakQuestions.push(questionSheet.getRange(i + 2, 1, 1, 5).getValues().flat());
この部分が複雑なため説明していく。
-
.getRnage()と.getValues()を使用し、「テストに出す英単語用」シートから苦手問題の問題情報(type、正解など)を得る -
.flat()で二次元配列を一次元配列に直す(.getValues()で取得しているため元は二次元配列) -
.push()でweakQuestionsに追加
苦手問題がなかった時の中断処理
苦手問題に該当する問題が0だった場合、そのままフォームの作成などに取りかかると以下のようなエラーが出る。

そのため、もしweakQuestionsが空だったらポップアップウィンドウが表示され、処理が中断されるようにする。
if (weakQuestions.length === 0) {
Browser.msgBox("苦手な問題がありません。OKを押して閉じてください。", Browser.Buttons.OK);
return;
}
この処理を書いておくことで、苦手問題がない場合も以下のようになり、エラーが出ない。
スプレッドシートに記録
「苦手問題」のシートに苦手問題として抽出されたものを記録しておく。
こうすることで苦手問題のみ復習したり、振り返ったりすることが容易になる。
weakSheet.getRange(2, 1, weakQuestions.length, 5).setValues(weakQuestions);
フォーム作成
make_test関数と同じ流れでフォームを作成していく。
const formTitle = Utilities.formatDate(new Date(), "JST", "yyyy/MM/dd_hh:mm:ss_英単語テスト・苦手問題すべて");
const form = FormApp.create(formTitle);
form.setIsQuiz(true);
form.setLimitOneResponsePerUser(true);
form.setDestination(FormApp.DestinationType.SPREADSHEET, ss.getId());
//フォームの説明文を追加
form.setDescription("苦手全問テストだよ!");
//フォームIDを取得して保存
const formId = form.getId();
PropertiesService.getScriptProperties().setProperty('FORM_ID', formId);
- フォームタイトル設定&フォーム作成
- 各種フォームの設定(クイズモードON・回答回数1回に制限・スプレッドシートと連携)
- フォームの説明文として「苦手全問テストだよ!」という文章を追加(
saiten関数で利用するため)
フォームに問題追加
//問題番号を保存する配列
const questionOrder = [];
//苦手問題として抽出した問題をフォームに追加していく
for (let j = 0; j < weakQuestions.length; j++) {
const [title, type, required, correct, number] = weakQuestions[j];
createItem(form, title, type, required);
questionOrder.push(number);
const range = answerSheet.getRange(answerLastRow + j + 1, 1, 1, 2);
range.setValues([[correct, number]]);
}
//問題番号を保存
PropertiesService.getScriptProperties().setProperty('PROBLEM_NUMBER', JSON.stringify(questionOrder));
ここでは、「抽出した苦手問題をフォームに追加し、後で参照するために問題番号を保存する」 処理を行っている。
コード解説(for文)
1. weakQuestionsの情報を受け取る
const [title, type, required, correct, number] = weakQuestions[j];
ここでは、要素が多くそれぞれ変数を宣言して代入するのが大変なので、分裂代入を利用している。
weakQuestions1行には5つの要素が入っているので、それらを各変数title, type, required, correct, numberに割り当てている。
※行ごとに要素数が異なる場合は、今回のように分裂代入しようとするとエラーが出るので注意。
2. フォームに問題追加
createItem(form, title, type, required);
-
make_test関数でも使用したcreateItemを使う。
3. 問題番号保存
questionOrder.push(number);
4. 回答収集用シートに記録
const range = answerSheet.getRange(answerLastRow + j + 1, 1, 1, 2);
range.setValues([[correct, number]]);
※setValuesするときは1行でも2次元配列とする点に注意
DiscordでフォームのURLを送信
この部分のコードはmake_testと同じなので、説明は割愛する。
//フォームURLを取得してスプレッドシートの「フォームのリンクと結果」シートに書き込む
const formUrl = form.getPublishedUrl();
urlSheet.getRange(1, 2, 2, 1).setValues([[formUrl], [""]]);
//フォームのURLをDiscordに送信
const payload = { "content": formUrl };
const options = { "method": "post", "payload": payload };
UrlFetchApp.fetch(WEBHOOK_URL, options);
さいごに
今回はmake_nigate関数の実装について解説しました。
次回はこの関数の挙動確認と、補助機能の追加をしていきます。

