SQLの窓

2020年03月28日


Google Classroom は無料の G Suite for Education アカウントが必要

個人アカウントで作ろうとしたら怒られたというお話



作成は本来 Google Apps Script の拡張 API から行いますが、こちらの Try this API で簡単に実行できます



結局使え無いんですが、きっちりフォルダは作られてしまうので削除が必要です




で、どうもコースの実体は残っているようなんですが、アーカイブできないので削除もできないというか...
一般アカウントでやる事では無いという結論です



posted by lightbox at 2020-03-28 15:45 | Google | このブログの読者になる | 更新情報をチェックする

2020年03月25日


教室と一対一のフォルダより新しく登録されたフォルダの中にあるZoom動画ファイルを該当するClassroom の コース内の該当するトピックに登録する

起動はトリガで夜間バッチです。

▼ 時間割テーブル


// *******************************************
// 教室と一対一のフォルダより新しく登録された
// フォルダの中にあるZoom動画ファイルを該当する
// Classroom の コース内の該当するトピック
// に登録する
// *******************************************
function myFunction() {

	// 教室用のフォルダ / 共有ドライブ に作成済
	// ( id は、表示されている URL より取得 )
	var folder = DriveApp.getFolderById("1o-C4Vn-2zBYn8ENOAvVwd3g8BNp9xS-E");
	var Iterator = folder.getFolders();

	// フォルダ内の一覧
	while (Iterator.hasNext()) {
		var folder = Iterator.next();

		// デバッグ用
		// GmailApp.sendEmail("メールアドレス", "Zoom 動画 フォルダ名", folder.getName());

		// 動画が格納されているフォルダ名( Zoom が自動作成したもの )
		var target = folder.getName();

		// Zoom フォルダ名より曜日番号を取得
		var youbiNum = getYoubi(target);

		var spreadsheet = SpreadsheetApp.getActive();

		var timeNum = getTime(spreadsheet, target);
		if (timeNum == -1) {
			continue;

		}

		var i = 1;
		var exist = false;

		while (true) {

			// 登録済のフォルダを排除する為に順に比較していく
			var targetRange = spreadsheet.getRange('A' + i);
			var cellWork = targetRange.getValue().toString();
			if (cellWork == '') {
				// 新規なので、スプレッドシートの A 列の最後に追加する
				targetRange.setValue(target);

				// MIME で絞ってファイル一覧を取得
				var files = folder.getFilesByType("video/mp4");
				// フォルダ内の動画ファイルをチェック
				while (files.hasNext()) {

					var file = files.next();

					// 拡張子 .mp4 のみを取得
					var work = file.getName();
					var exts = work.split(".");
					if (exts[1] != "mp4") {
						continue;

					}

					// Google ドライブ上の 動画の ID
					var fileId = file.getId();
					var courseId = getCourseId(spreadsheet, youbiNum, timeNum);
					// 本来は条件を元に決定する
					var topicId = getTopicId(spreadsheet, youbiNum, timeNum);

					// 課題作成用の JSON
					var json = {
						"materials": [
						{
							"driveFile": {
								"driveFile": {
									"id": fileId

								},
								"shareMode": "VIEW"

							}

						}
						],
						"description": "動画の補足説明",
						"assigneeMode": "ALL_STUDENTS",
						"state": "PUBLISHED",
						"title": target,
						"topicId": topicId,
						"workType": "ASSIGNMENT"

					};

					// デバッグ用
					// GmailApp.sendEmail("メールアドレス", "JSON", JSON.stringify(json));


					// 課題を投稿
					try {
						Classroom.Courses.CourseWork.create(json, courseId);

					}
					catch(e) {
						targetRange.setValue("");
						GmailApp.sendEmail("メールアドレス", "Classroom 投稿エラー", target);


					}

				}

				break;

			}
			else {
				if (cellWork == target) {
					exist = true;
					break;

				}
				else {
					i++;

				}

			}

		}


	}



}

// *******************************************
// Zoom フォルダ名より曜日番号を取得
// *******************************************
function getYoubi(folderName) {

	var ymdString = folderName.substr(0, 10);
	var dateData = new Date(ymdString);
	var youbiNum = dateData.getDay();

	return youbiNum;

}

// *******************************************
// Zoom フォルダ名より時間割テーブル上の
// 時限番号を取得
// *******************************************
function getTime(spreadsheet, folderName) {

	var timeNum = -1;

	var rangeData1 = [];
	var rangeData2 = [];

	// C 列にある時限の時間範囲を配列で取得
	for (var i = 1; i < 10; i++) {
		rangeData1.push(spreadsheet.getRange("C" + (4 + 4 * (i - 1))).getValue().toString());
		rangeData2.push(spreadsheet.getRange("C" + (5 + 4 * (i - 1))).getValue().toString());

	}

	//  var rangeData1 = ["09:20:00", "11:00:00", "13:30:00", "15:10:00"];
	//  var rangeData2 = ["10:59:00", "12:40:00", "15:09:00", "16:50:00"];

	// 対象の時刻部分を取得
	var ymdString = folderName.substr(0, 19);
	ymdString = ymdString.replace(/\./g, ":")
	var dateData = new Date(ymdString);

	// 範囲を持つ配列より時限を決定
	for (var i = 0; i < 9; i++) {
		if (rangeData1[i] != "") {
			var rangeWork1 = new Date(folderName.substr(0, 10) + " " + rangeData1[i]);
			var rangeWork2 = new Date(folderName.substr(0, 10) + " " + rangeData2[i]);
			if (rangeWork1 <= dateData && dateData <= rangeWork2) {
				timeNum = i + 1;

			}
		}
	}

	return timeNum;

}


// *******************************************
// 曜日と時限よりコースを取得
// *******************************************
function getCourseId(spreadsheet, youbiNum, timeNum) {

	var youbiRange = ["", "D", "E", "F", "G", "H", "I"]

	var targetRange = spreadsheet.getRange(youbiRange[youbiNum] + (2 + 4 * (timeNum - 1)));
	var courseId = targetRange.getValue().toString();

	return courseId;


}

// *******************************************
// 曜日と時限よりトピックを取得
// *******************************************
function getTopicId(spreadsheet, youbiNum, timeNum) {

	var youbiRange = ["", "D", "E", "F", "G", "H", "I"]

	var targetRange = spreadsheet.getRange(youbiRange[youbiNum] + (3 + 4 * (timeNum - 1)));
	var topicId = targetRange.getValue().toString();

	return topicId;


}

// *******************************************
// 作業用のコースID 一覧( 日本語名も ) を K・L
// 列に作成
// トピック一覧も N・O 列に作成
// *******************************************
function setCourseId() {

	var spreadsheet = SpreadsheetApp.getActive();

	var json = Classroom.Courses.list();

	var cnt = json.courses.length;

	for (var i = 0; i < cnt; i++) {
		var targetRange = spreadsheet.getRange('K' + (i + 1));
		targetRange.setValue(json.courses[i].id);
		targetRange = spreadsheet.getRange('L' + (i + 1));
		targetRange.setValue(json.courses[i].name);

	}

	var jsonTopic = null;

	var l = 0;
	for (var i = 0; i < cnt; i++) {
		jsonTopic = Classroom.Courses.Topics.list(json.courses[i].id);
		try {
			for (var j = 0; j < jsonTopic.topic.length; j++) {
				var targetRange = spreadsheet.getRange('N' + (l + 1));
				targetRange.setValue(jsonTopic.topic[j].topicId);
				targetRange = spreadsheet.getRange('O' + (l + 1));
				targetRange.setValue(jsonTopic.topic[j].name);
				l++;

			}

		}
		catch(e) {
		}


	}

}





posted by lightbox at 2020-03-25 23:02 | Google | このブログの読者になる | 更新情報をチェックする

2020年03月19日


指定した位置でレジストリエディタを開く為の VBScript をダウンロードします

コンボボックスにいくつかサンプルを用意しています。入力フィールドに直接セットしてもいいですし、いったんダウンロードしたソースの先頭のパスを変更しても OK です。

理屈としては、regedit は、終了する時に表示していたパスをレジストリに保存しており、その場所に書き込む事によって regedit を起動した時に好きな場所が表示されるようにしています。

▼ 保存場所
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Applets\Regedit\LastKey
※ IE11 ソースエディタは、インターネットオプションのプログラムでメモ帳を選択してからアクセスします


▼ 一度ダウンロードしたソースコードの先頭の strParam にレジストリのパスを設定するだけで使えます
strParam = "HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\ODBC\ODBCINST.INI"

' レジストリ書き込み用
Set WshShell = CreateObject( "WScript.Shell" )
' WMI用
Set objWMIService = GetObject("winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2")

' レジストリエディタが最後に開いていたキーの登録を行います
strPath = "Software\Microsoft\Windows\CurrentVersion\Applets\Regedit\LastKey"
if GetOSVersion() >= 6 then
	strRegPath = "コンピューター\" & strParam
else
	strRegPath = "マイ コンピュータ\" & strParam
end if

' 既に regedit が実行中の場合はいったん終了させます
Set colProcessList = objWMIService.ExecQuery _ 
	("Select * from Win32_Process Where Name = 'regedit.exe'") 
For Each objProcess in colProcessList
	' 最後のウインドウの位置とサイズを保存する為の終わらせ方
	WshShell.AppActivate("レジストリ エディタ")
	Wscript.Sleep(500)
	WshShell.SendKeys ("%{F4}")
	Wscript.Sleep(500)
	' 上記終わらせ方が失敗した時の強制終了
	on error resume next
	objProcess.Terminate() 
	on error goto 0
Next 

WshShell.RegWrite "HKCU\" & strPath, strRegPath, "REG_SZ"

' レジストリエディタを起動します
Call WshShell.Run( "regedit.exe" )
' レジストリエディタが終わるまで待つ場合は以下のようにします
' Call WshShell.Run( "regedit.exe", , True )

REM **********************************************************
REM OS バージョンの取得
REM **********************************************************
Function GetOSVersion()

	Dim colTarget,str,aData,I,nTarget

	Set colTarget = objWMIService.ExecQuery( _
		 "select Version from Win32_OperatingSystem" _
	)
	For Each objRow in colTarget
		str = objRow.Version
	Next

	aData = Split( str, "." )
	For I = 0 to Ubound( aData )
		if I > 1 then
			Exit For
		end if
		if I > 0 then
			nTarget = nTarget & "."
		end if
		nTarget = nTarget & aData(I)
	Next

	GetOSVersion = CDbl( nTarget )

End Function





posted by lightbox at 2020-03-19 13:43 | VBScript | このブログの読者になる | 更新情報をチェックする

2020年03月08日


Google Apps Script : 動画を添付して Classroom の指定のトピックへ課題として投稿する

関連する記事

Google Apps Script : 時間主導のトリガを使用して、CRON のように一定間隔でスクリプトを実行する


このコードは本来、時間主導のトリガを使用して、投稿していない動画があった場合に実行するものです。

( ✅注意 ) いろいろ調べましたが、『資料の作成』は、API からは実行できるようになっていませんでした。
function myFunction() {

	// https://any-api.com/googleapis_com/classroom/docs/courses/classroom_courses_courseWork_create

	// コースID は最初に取得しておいて、コース毎のトリガを作成する
	var courseId = "35126354603"  // クラス番号

	// スプレッドシートより、動画が保存されているフォルダIDを取得
	var spreadsheet = SpreadsheetApp.getActive();
	spreadsheet.getRange('A1').activate();
	var target = spreadsheet.getCurrentCell().getValue();

	var folder_id = target;  // 動画のあるフォルダID
	var target_folder = DriveApp.getFolderById(folder_id);

	var files = target_folder.getFilesByType("video/mp4"); // mp4 の一覧

	// フォルダ内の動画ファイルをチェック
	while( files.hasNext() ) {

		var file = files.next();
		// 本来はこのid が既に存在している場合は continue ( チェックは スプレッドシートを利用する )
		var fileId = file.getId();
		// 本来は条件を元に決定する
		var topicId = "53042801088";  // 課題A( スプレッドシートを利用する )

		// 課題作成用の JSON
		var json = {
			"courseId" : courseId,
			"materials": [
				{
					"driveFile": {
						"driveFile": {
							"id": fileId
						},
						"shareMode": "VIEW"
					}
				}
			],
			"description": "動画の補足説明",
			"assigneeMode": "ALL_STUDENTS",
			"state": "PUBLISHED",
			"title": "動画のタイトル",
			"topicId": topicId,
			"workType": "ASSIGNMENT"
		};

		// 課題を投稿
		Classroom.Courses.CourseWork.create(json, courseId);
	}
}




posted by lightbox at 2020-03-08 14:45 | Google | このブログの読者になる | 更新情報をチェックする

2020年03月07日


Google Apps Script : 時間主導のトリガを使用して、CRON のように一定間隔でスクリプトを実行する

関連する記事

Google Apps Script(GAS) で、Google Classroom に投稿する


一定間隔で、Classroom になんらかの授業用データを作成する為の準備用作業です。

▼ まず Google スプレッドシートを作成してスクリプトエディタを起動します


▼ コースid ( 整数文字列 ) と コース名をメールに送信するコードを保存


▼ スクリプト名を決定して保存


▼ Google の拡張サービスで、Classroom の API を使用可能にする




▼ トリガ登録画面を表示する


▼ トリガ登録


▼ トリガの時間間隔を決定


このスクリプトは、指定のメールアドレスに現在存在する コースid( 自分はクラス番号と呼んでます ) と名前を送っています。
function myFunction() {

	var response = Classroom.Courses.list({});
	var courses = response.courses;
	var course = "";
	for (i = 0; i < courses.length; i++) {
		course = courses[i];
		var now = new Date();
		GmailApp.sendEmail("メールアドレス",
						"Classroom の id と名前",
						course.id + " : " + course.name + " : " +now.toString()
		);
	}

}




posted by lightbox at 2020-03-07 20:38 | Google | このブログの読者になる | 更新情報をチェックする

Google Apps Script(GAS) で、Google Classroom に投稿する



ドキュメントでは、REST API なので、UrlFetchApp クラスを使うのか? とか思ってしまいますが、そもそも それだと auth が必要なのでまだ試してしません。

で、そんな事をしなくてもそれぞれの REST の クラスのメソッドに引数を渡す形で実行できます。
但し、Classroom Service が拡張なので、スクリプトエディタのリソースメニュー(Google の拡張サービス)より API を実行可能にしておく必要があります。



以下のサンプルでは、Classroom は一つしか作成しておらず、ループである必要はありませんが、複数の場合に名前で特定する時に使えると思います。
function createAnnouncements(){
	
	// **************************************************
	// 現在は1つしか無いので、対象が course.id となります
	// **************************************************
	var response = Classroom.Courses.list({});
	var courses = response.courses;
	var course = "";
	for (i = 0; i < courses.length; i++) {
		course = courses[i];
		Logger.log('%s (%s)', course.name, course.id);
	}

	// **************************************************
	// POST する JSON	
	// **************************************************
	var data = {
		"courseId" : course.id, 
		"materials": [
			{
				"link": {
					"url": "https://news.google.com/?hl=ja&tab=rn1&gl=JP&ceid=JP:ja",
					"title": "Google ニュース",
					"thumbnailUrl": ""
				}
			}
		],
		"text": "Google Apps Script による Classroom への投稿",
		"assigneeMode": "ALL_STUDENTS",
		"state": "PUBLISHED"
	};

	Classroom.Courses.Announcements.create(data, course.id);

}


Logger.log は、何かと結果が表示されない事が多いので、Gmail で自分のアドレスに送信したほうがいいと思います。
// The code below will send an email with the current date and time.
var now = new Date();
GmailApp.sendEmail("mike@example.com", "current time", "The time is: " + now.toString());

関連する記事

Google Classroom のテーマ画像のサイズと既存画像をテーマ画像として使用してみた手順


このページの PDF



posted by lightbox at 2020-03-07 14:04 | Google | このブログの読者になる | 更新情報をチェックする
Seesaa の各ページの表示について
Seesaa の 記事がたまに全く表示されない場合があります。その場合は、設定> 詳細設定> ブログ設定 で 最新の情報に更新の『実行ボタン』で記事やアーカイブが最新にビルドされます。

Seesaa のページで、アーカイブとタグページは要注意です。タグページはコンテンツが全く無い状態になりますし、アーカイブページも歯抜けページはコンテンツが存在しないのにページが表示されてしまいます。

また、カテゴリページもそういう意味では完全ではありません。『カテゴリID-番号』というフォーマットで表示されるページですが、実際存在するより大きな番号でも表示されてしまいます。

※ インデックスページのみ、実際の記事数を超えたページを指定しても最後のページが表示されるようです

対処としては、このようなヘルプ的な情報を固定でページの最後に表示するようにするといいでしょう。具体的には、メインの記事コンテンツの下に『自由形式』を追加し、アーカイブとカテゴリページでのみ表示するように設定し、コンテンツを用意するといいと思います。


※ エキスパートモードで表示しています

アーカイブとカテゴリページはこのように簡単に設定できますが、タグページは HTML 設定を直接変更して、以下の『タグページでのみ表示される内容』の記述方法で設定する必要があります

<% if:page_name eq 'archive' -%>
アーカイブページでのみ表示される内容
<% /if %>

<% if:page_name eq 'category' -%>
カテゴリページでのみ表示される内容
<% /if %>

<% if:page_name eq 'tag' -%>
タグページでのみ表示される内容
<% /if %>
この記述は、以下の場所で使用します
container 終わり



フリーフォントで簡単ロゴ作成
フリーフォントでボタン素材作成
フリーフォントで吹き出し画像作成
フリーフォントではんこ画像作成
ほぼ自由に利用できるフリーフォント
フリーフォントの書体見本とサンプル
画像を大きく見る為のウインドウを開くボタンの作成

CSS ドロップシャドウの参考デモ
イラストAC
ぱくたそ
写真素材 足成
フリーフォント一覧
utf8 文字ツール
右サイド 終わり
base 終わり