2023-10-24

GAS: Google Classroomの受講生のメールアドレスを取得する

Pythonのサンプルは既に挙げていますが、GAS版も。。やっぱりこちらのほうがコードもスッキリします。

function getUserEmailsInCourse(courseId) {
var emailList = [];
var nextPageToken;
do {
var response = Classroom.Courses.Students.list(courseId, {
pageToken: nextPageToken
});
var students = response.students;
if (students && students.length > 0) {
for (var i = 0; i < students.length; i++) {
var userId = students[i].userId;
var email = students[i].profile.emailAddress;
emailList.push({
userId: userId,
email: email
});
}
}
nextPageToken = response.nextPageToken;
} while (nextPageToken);
return emailList;
}


2023-10-16

Python: GoogleのOAuth認証の仕組みを理解する

すぐ忘れてしまうのでメモ書き。Googleのサービスにアクセスする際には、次の手順が必要です。なお、本記事は、GoogleClassroomAPI を利用する過程での処理なので、適時読み替えてください。

Google Cloud コンソールの設定と認証ファイルの作成
まずは、Google Cloudコンソールで、GoogleClassroomAPIを使えるようにしないといけません。詳細の手続きは、公式サイトを参照してください。ここで、大切なのは

  • Google Cloudプロジェクトを作成しておく(以下、全てこのプロジェクト内での操作になります)
  • Google Classroom API を有効にする(利用したいAPIをONにしましょう。細かな権限設定もできます)
  • 「APIとサービス」のところで認証情報に移動し、認証ファイルを作成しダウンロード
Google クライアントPythonライブラリのインストール
先程の公式サイトに書いてあるので、pip でインストールしましょう。

サンプルコードの実行
下記は、公式サイトの一部を抜粋したものです。ここで理解しておくべきことは、認証ファイルとトークンファイルについてです。
  • 認証ファイルは上記でダウンロードしたファイルです。コード内のcredentials.jsonに相当します
  • トークンファイルは、このコードを実行することで生成されるファイルです。このコードを実行するとブラウザが立ち上がり、認証を求められます。認証を済ませると、token.jsonファイルが作成されます。このトークンファイルは古くなると使えなくなるので定期的に消して書き換えることになるのだと思います
注意点
トークンファイル(token.json)は、スコープに関係しています。Google Cloud コンソールにてプロジェクトで扱えるスコープを設定したと思いますが、クライアント側(コード内)でも指定しないといけません。それが、コード内でのSCOPES変数に相当します。下記のサンプルでは、1つだけ指定してますが、配列として複数指定することは可能です。ここに書いたSCOPESに対応したトークンファイルが出来上がることになります。ですので、SCOPESを変更するときは、トークンファイルを消して再度生成し直してください。

from __future__ import print_function
import os.path
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError
# If modifying these scopes, delete the file token.json.
##SCOPES = ['https://www.googleapis.com/auth/classroom.courses.readonly']
def main():
creds = None
# The file token.json stores the user's access and refresh tokens, and is
# created automatically when the authorization flow completes for the first
# time.
if os.path.exists('token.json'):
creds = Credentials.from_authorized_user_file('token.json', SCOPES)
# If there are no (valid) credentials available, let the user log in.
if not creds or not creds.valid:
if creds and creds.expired and creds.refresh_token:
creds.refresh(Request())
else:
flow = InstalledAppFlow.from_client_secrets_file(
'credentials.json', SCOPES)
creds = flow.run_local_server(port=0)
# Save the credentials for the next run
with open('token.json', 'w') as token:
token.write(creds.to_json())


2023-10-01

GAS:GoogleシートのデータからGoogleフォーム(選択肢問題)を生成する

 講義で選択肢問題を準備する機会が増えてきたので作ってみました。GASです。Googleシートに

  • 問題文
  • 正解選択肢文字
  • 得点
  • 選択肢‥
というならびでシートに作成しておき、下記のGASを動かせば作成できます。注意点は、このサンプルでは、正解選択肢の設定に、正解選択肢文字列との比較を用いていることです。補足すると、このケースでは、選択肢として「アイウエ」という選択肢ラベルのみを用意しています。(アイウエが何を表しているのは問題別紙に書いているということです)

ですので、もし選択肢を単なるアイウエではなく、文字列にしたいなら、2列目は世界選択肢文字ではなく、正解選択肢が何番目なのかを記述して、コードも修正したほうがいいです。(機会があれば、そのバージョンも用意します)

function createQuizForm() {
var row=10; //問題数
var col=7; //シートの列数
var form = FormApp.create('クイズフォーム(自動生成)'); // フォームのタイトルを設定
form.setIsQuiz(true);
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
//シートのデータを全部取得
var data = sheet.getDataRange().getValues();
for (var i = 1; i <= 10; i++) { // 先頭のヘッダー行をスキップ
var questionTitle = data[i][0]; //1列目に問題文
var correctAnswer = data[i][1]; //正解情報(正解選択肢ラベル)
var point = data[i][2]; // 得点
var choices = data[i].slice(3, col); // 選択肢は2列目から4列目まで
var item = form.addMultipleChoiceItem();
item.setTitle(questionTitle);
var choiceArray = [];
for(var j=0;j<choices.length;j++){
//item.setChoiceValues(choices)
var cTitle=choices[j];
if(choices[j]==correctAnswer){ //正解情報と選択肢ラベルの文字列一致で正解箇所設定
choiceArray.push(item.createChoice(choices[j],true));
}
else{
choiceArray.push(item.createChoice(choices[j],false));
}
}
item.setChoices(choiceArray);
item.setPoints(point);
}
}