【GAS】Google Classroomで、生徒に個別に連絡をする。

公立高校の教員(理科・物理)です。初のQiita投稿です！

# 作ったもの
スプレッドシートで、アプリケーションを作成しました。Excelのように、ボタンをクリックするとコードが実行されます。
![classroom個別投稿.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1060178/d25ff92f-e17b-9a30-4a13-7b9a433376d5.png)
[こちらのリンク](https://docs.google.com/spreadsheets/d/1OerpSFRMqkLd7eelVJwcFB3-rQQVGverd8gbDwW2si4/copy)からコピーを保存できます。この記事を読んで興味を持ってくださった方は、ご自身のドライブから実行してみてください。


# なにができるのか
* Google Classroomのストリームには、生徒を指定した限定公開の機能があります。このソフトでは、限定公開の機能を使って、スプレッドシートに事前に記録した内容を、生徒一人ひとりに限定公開で配信をします。
* 今までExcel等で作成し、印刷していた生徒個票を、Classroom上で配信することで代替できます。
  * 使用例：出欠日数のおしらせ、提出物提出回数の確認、各種アカウントの配布
* 操作ミスを防ぐために、スプレッドシートのメッセージボックスやインプットボックスを使って、多くの学校現場で使われているであろう、ExcelVBAで作られたソフトウェアと見た目を似せてみました。
* 機能面や運用面のことはさておき、Qiitaでは、GASの技術面についてコメントを残します。

# 作成した関数の紹介
* その前に...公式リファレンス等では、classroomを識別する値をcourseIdとしていますが、私が作成したプログラムでは、変数名をclassIdとしてしまっています。その２つは、同様のものをさしていると思って読み進めてください。

## getClassListSS 関数
* 自分が担当している授業名とコースIDをスプレッドシートに出力します。
* コースIDとは、classroomに生徒を招待するときに使う「クラスコード」とは別物で、GASなどで各クラスを扱うときに使う識別番号です。

```Javascript
function getClassListSS() {
  var response = Classroom.Courses.list();
  var courses = response.courses;
  var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = spreadsheet.getSheetByName('担当クラスリスト');
  sheet.clear();
  sheet.appendRow(["授業名", "クラスコード"]);
  for (i = 0; i < courses.length; i++) {
    Logger.log('%s (%s)', courses[i].name, courses[i].id);
    sheet.appendRow([courses[i].name, courses[i].id]);
  }
  sheet.activate();
  Browser.msgBox("【担当クラスリスト】のシートに、授業のリストを作りました。クラスコードをコピーしてください。");
}
```

* sheet.appendRow(配列)が、便利でした。

## createStudentListMaxSS 関数
* getClassListSS関数で取得した担当授業一覧のコースIDを使います。
* コースIDで指定したクラスの、参加生徒一覧を取得してスプレッドシートに書き出します。
* コースIDは、文字列型で渡す必要があるので、String(コースId)として、型変換をしています。(これに気づかずに、少し時間を食いました。)

```Javascript
function createStudentListMaxSS() {
  var classId = Browser.inputBox("生徒リストを作成します。classroomのIDを入力してください。\n わからない場合は、キャンセルを押してください。", Browser.Buttons.OK_CANCEL); //ClassroomのIDを入力
  if (classId != "cancel") {
    classId = String(classId);
    className = getClassName(classId);
    Logger.log(className);
    var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
    var sheet = spreadsheet.getSheetByName('生徒リスト');
    sheet.clear();
    sheet.appendRow(["クラスコード", "アドレス", "生徒名", "連絡事項(はじめは空欄)", "送信状況(送信済みは、空欄)"]);

    // 取得したクラスIDから、そのクラスに所属している生徒リストを取得
    var students = getStudentListMax(classId);

    //　生徒一覧をログとして出力
    for (i = 0; i < students.length; i++) {
      Logger.log(students[i][0]);//生徒のアドレス出力
      Logger.log(students[i][1]);//生徒の名前を出力
      sheet.appendRow([classId, students[i][0], students[i][1], "ここに記入", "これから送信する"]);
    }
    sheet.getRange('A1').activate();

    //作成したスプレッドシートのURLをログに表示
    Browser.msgBox("生徒リストを作成しました。生徒リストのD列に、配信する文章を記録してください。");
  } else {
    Browser.msgBox("キャンセルが押されました。");
  }
}
```

* getStudentListMaxは、コースIDを引数に取る、自作関数です。
    * 標準搭載の関数　Classroom.Courses.Students.list(コースID)　だと、生徒数30人までしか取得できません。[公式リファレンス](https://developers.google.com/classroom/reference/rest/v1/courses.students/list)を読むと、引数のpageSizeをいじればいいと書いてありますが、何度やっても30人以上取得できませんでした(まあ、そんなもんなんでしょう。)。
    * [こちらの記事](https://qiita.com/coticoticotty/items/17638685b8969b974adb)を参考に、getStudentListMax関数を作成しました。
 
```Javascript
function getStudentListMax(classId) {
  let pageToken = "";
  var cnt = 0;
  let allStudentList = [];// 空の配列を用意
  do {
    let studentList = Classroom.Courses.Students.list(classId, { "pageToken": pageToken });
    for (i = 0; i < studentList['students'].length; i++) {
      let student = studentList['students'][i];
      allStudentList.push([student['profile']['emailAddress'], student['profile']['name']['fullName']]);//生徒のアドレスを配列に追加
    }
    pageToken = studentList.nextPageToken;//しおりを挟む。最後のページでなければまた「do」する
    cnt = cnt + 1;
  } while (pageToken);//最後のページまで行ったら処理を辞める
  return allStudentList;
}
```

* 引数にコースIDを取り、メールアドレスとフルネームを戻り値とします。allStudentListに格納する値を変えれば、ほかの値もできます。
     * JSONを戻り値にすればよかったのかもしれませんが、自分の知識不足でうまく扱えませんでした...。 

## listToClassroom 関数
* 最後に、投稿する関数です。
* 誤投稿を防ぐために、msgBoxで処理を分けています。

```Javascript
function listToClassroom() {
  var ck = Browser.msgBox("Okを押すと、生徒に個別配信されます。", "続けますか", Browser.Buttons.OK_CANCEL);
  if (ck == 'ok') {
    var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
    var sheet = spreadsheet.getSheetByName('生徒リスト');
    const lastRow = sheet.getLastRow();
    for (let i = 2; i <= lastRow; i++) {
      if (sheet.getRange(i, 5).getValue()) { //空欄の場合は、送信しない。
        const values = sheet.getRange(i, 1, 1, 4).getValues();
        console.log(values);
        postAnounceIndividual(values[0][0], values[0][1], values[0][2], values[0][3]);
        sheet.getRange(i, 5).clearContent();
      }
    }
    Browser.msgBox("ストリームに、個別の連絡を送信しました。");
  }
  if (ck == 'cancel') {
    Browser.msgBox("キャンセルを押しました");
  }
}
```
* postAnounceIndividual関数は、[コースID、生徒のアドレス、生徒のフルネーム、投稿する文章]の4つの引数をとって、Classroom　APIを使って実際に投稿をする関数です。

```Javascript
function postAnounceIndividual(target, mailAdressStr, nameStr, textStr) {

  studentList = [mailAdressStr];
  var data = {
    "courseId": String(target),
    "text": nameStr + "さんへ(個別連絡です。) \n \n " + textStr,
    "assigneeMode": "INDIVIDUAL_STUDENTS",
    "individualStudentsOptions": {
      "studentIds": studentList//スプレッドシートから取得した生徒のIDリストを渡す
    },
    "state": "PUBLISHED"
  };
  Classroom.Courses.Announcements.create(data, String(target));
  Logger.log(studentList + "_" + textStr + "______anounce!!");
}
```
* Classroom.Courses.Announcements.create()の引数にJSON形式で投稿するテキストや送信相手のメールアドレスを指定することで、Classroomに投稿ができます。公式リファレンスは[こちら](https://developers.google.com/classroom/reference/rest/v1/courses.announcements#Announcement)。
* "assigneeMode"と、"studentIds"が、個別投稿のキモです。(でも正直よくわかっていませんw)

## getClassName 関数
* 最後におまけで、コースIDを引数に、授業名を戻す関数です。この手の処理は、探して見ると意外と見つからないものです。

```Javascript
function getClassName(targetClassId) {
  //ClassIdから、授業名を返します。
  var myCourses = Classroom.Courses.list().courses;

  ////取得したいクラスの名前を、IDから取得
  for (i = 0; i < myCourses.length; i++) {
    if (myCourses[i]['id'] === targetClassId) {//targetClassNameに一致するときのみ以下の処理を実施
      var inviteClassName = myCourses[i]['name'];
    } else {
      continue;
    }
  }
  return inviteClassName;
}
```

# 最後に
* トリガーと連携させれば、「formが提出されたらclassroomでお知らせする」などもできると思います。
* 「メールでやればいいじゃん！」と思った方へ、確かに、臨時休業中は、私もメールで連絡や提出チェック等を行っていました。
    * しかし、高校生の生徒にとって、メールは身近ではありません。学校で使うから...という理由で、メールアプリをインストールさせて、学校アドレスを登録して...なんていうのは、生徒・教員ともども負担が大きいです。
    * 一方、クラスルームは朝の連絡や健康観察等で毎朝アクセスさせているので、比較的高い割合で確認してくれます。
* GIGAスクールなんちゃらで、多くの自治体がGoogle Classroom等を利用していると思います。GASを使うことで、様々な校務の効率化ができると思います。今後も、GASに関する投稿ができればいいなと思います。

# 2021/06/16追記
* [Gmail禁止でも大丈夫！生徒個別メッセージスクリプトでGoogle Classroom上で個別相談！](https://www.youtube.com/watch?v=dgdRlCutPrQ)で、上記のコードを紹介していただきました。ちょびっと嬉しいです。