この記事は、GAS(Google Apps Script)とDiscordを連携させた英単語テスト自動作成システムの開発記録・第4回です。
前回の記事では、 make_test 関数の実装①について説明しました。
今回は、 make_test の実装を最後までやっていきます。
make_test関数作成②
前回まででスプレッドシートの取得・フォームの作成を行ったので、今回は以下の作業をやっていく。
- スプレッドシートから問題データを取得
- 全問題からランダムで数問選んでフォームに設定する
- Discordに作成した英単語テストのURLを送信
問題データの取得
「テストに出す英単語用」のシートからすべての問題データを取得する。
//シートの値を取得
//返り値は2次元配列
const wordsData = questionSheet.getRange(1, 1, questionLastRow, questionLastCol).getValues();
フォームに問題を設定
先に出題数を決める変数を作る。これは、「プログラム実行中に再代入しない&全関数で使用する」ので、グローバル変数として定義する。
よって以下のように変更を加える。
const WEBHOOK_URL = "https://xxx....";
function make_test(){
//既存のコード
}
const WEBHOOK_URL = "https://xxx....";
//ここを追加
const QUESTION_COUNT = 5;
function make_test(){
//既存のコード
}
QUESTION_COUNT は現在 5 に設定しているので、実行時には 5 問が出題される。
次に、ランダムで問題を選んでフォームに追加していく。
コードは以下のようになる。
//既出の乱数を保存する配列
const usedIndices = [];
//問題番号を保存する配列
const questionOrder = [];
//problem_numberの回数だけfor文を繰り返して問題を選択&追加していく
for (let i = 0; i < QUESTION_COUNT; i++) {
//出題する問題の問題番号が入るリスト
let selectedIndex;
//既出の乱数以外が出るまでwhile文を繰り返し、ランダムで問題を選ぶ
while (true) {
let rand = Math.floor(Math.random() * (questionLastRow - 1));
if (!usedIndices.includes(rand)) {
selectedIndex = rand;
usedIndices.push(rand);
break;
}
}
const title = wordsData[selectedIndex + 1][0]; //各問題
const type = wordsData[selectedIndex + 1][1]; //各問題タイプ
const required = wordsData[selectedIndex + 1][2]; //各問題に回答が必須かどうか
createItem(form, title, type, required); //事前に作成済みの関数createItemでFormに設問を追加する
const questionNumber = wordsData[selectedIndex + 1][4]; //各問題の問題番号
questionOrder.push(questionNumber); //questionOrderという変数に出題した問題の問題番号を保存する
const correctAnswer = wordsData[selectedIndex + 1][3]; //各問題の正解
const writeRange = answerSheet.getRange(answerLastRow + i + 1, 1, 1, 2); //回答収集用のシートで書き込む範囲を取得
writeRange.setValues([[correctAnswer, questionNumber]]); ////回答収集用のシートに正答・問題番号を設定する
}
コード解説
上記のコードでは、「全問題の中からランダムに5問を選び、フォームに追加しつつ、回答シートにも正答情報を書き込む」 という処理を行っている。
1.使用する配列の詳細
//既出の乱数を保存する配列
const usedIndices = [];
//問題番号を保存する配列
const questionOrder = [];
-
usedIndices:ランダムで選ぶ際に重複を防止するための配列 -
questionOrder:後で回答を採点するために、出題した問題の問題番号を記録する配列
2.ランダムに問題を選ぶ部分
//既出の乱数以外が出るまでwhile文を繰り返し、ランダムで問題を選ぶ
while (true) {
let rand = Math.floor(Math.random() * (questionLastRow - 1));
if (!usedIndices.includes(rand)) {
selectedIndex = rand;
usedIndices.push(rand);
break;
}
}
- Math.random() を使って 0〜(問題数−1) の範囲で乱数を生成
- 過去に選んだ乱数が出たらやり直し(重複防止)
- 未使用の乱数を見つけたら selectedIndex に採用
3.選んだ問題をフォームに追加する
const title = wordsData[selectedIndex + 1][0]; //各問題
const type = wordsData[selectedIndex + 1][1]; //各問題タイプ
const required = wordsData[selectedIndex + 1][2]; //各問題に回答が必須かどうか
createItem(form, title, type, required); //事前に作っていた関数createItemでFormに設問を作る
-
createItem関数の引数として「フォームID」、スプレッドシートの「問題」、「type」、「必須」列の情報が必要なので、それらを取得 -
+1をしているのは、wordsDataでヘッダー行も取得しているため(ここで+1するのが面倒だったらwordsDataの取得方法を変えればOK)
4.出題した問題の問題番号を配列に追加
const questionNumber = wordsData[selectedIndex + 1][4]; //各問題の問題番号
questionOrder.push(questionNumber); //questionOrderという変数に出題した問題の問題番号を保存する
- ループが終わると
questionOrderにすべての出題した問題の問題番号が記録される
5.回答収集用シートに正答を書き込む
const correctAnswer = wordsData[selectedIndex + 1][3]; //各問題の正解
const writeRange = answerSheet.getRange(answerLastRow + i + 1, 1, 1, 2); //回答収集用のシートで書き込む範囲を取得
writeRange.setValues([[correctAnswer, questionNumber]]); ////回答収集用のシートに正答・問題番号を設定する
-
answerLastRow + i + 1にすることで、毎回下の行に連続して書き込んでいる
DiscordでフォームのURLを送信
問題を設定するforループ終了後の処理は以下の通りになる。
//出題した問題の問題番号を記録した配列をスクリプトプロパティに保存
PropertiesService.getScriptProperties().setProperty('PROBLEM_NUMBER', JSON.stringify(questionOrder));
//フォームURLを取得
const formUrl = form.getPublishedUrl();
//「フォームのリンクと結果」のシートにフォームのリンクを書き込む
urlSheet.getRange(1, 2, 2, 1).setValues([[formUrl], [""]]);
//Discordに送信
const payload = { "content": formUrl }; //メッセージ内容
const options = {
"method": "post", //HTTP POST メソッドで送信
"payload": payload //送信データ
};
UrlFetchApp.fetch(WEBHOOK_URL, options); //Webhook URL へリクエストを送信
- フォームへの問題追加が終わったら、問題番号の配列をPropertiesサービスを利用して保存する(これ以降の関数でも使えるようになる)
- フォームの公開リンクを取得する
- スプレッドシートの「フォームのリンクと結果」のシートに取得したリンクを書き込む
- Webhookを利用してDiscordに送信
※今回はほとんど心配しなくて良いが、メッセージが長すぎたり特殊文字が入ると送信失敗する可能性があるので注意。
さいごに
今回はmake_test関数の実装完了までについて紹介しました。
ここまででテスト作成&送信はできるようになりました。
次回はこの関数の挙動確認と、別の関数の実装をやっていきます。