takaremonnの備忘録

takaremonnの備忘録

ソフトウェア 趣味

Google Classroomの投稿をLINEに転送するボットを作ってみた

投稿日:10月 30, 2020 更新日:

こんにちは
takaremonnです。
ようやく最近、WordPress投稿画面でシフトキーとエンター同時押し(同時ではないけど)で段落を変えずに改行ができる(Wordで言う↩と↓みたいな)ことに気づいた昨今です。
コントロール+エンターは知ってはいたものの、、まさかこっちとは…

まぁそのあたりのことは置いといて、、

なんと..

コロナウイルス感染拡大によるオンライン授業(年寄りの教員の方はサテライト授業と言うが)のためなのかよく分からないが、やっと自分が所属している高校でGoogle Classroomというツールを使うようになりました!!!

しかもそれに伴って、、

生徒ひとりひとりにG Suite for Educationの組織アカウントが割り当てられました!!!

これが意味することは、

  • Google Drive容量無制限
  • PCでネットワークドライブとしてマウントできるGoogleファイルストリームという公式ソフトが利用可能
  • Google App Scriptの制限が大幅に緩和 (1日にURL Fetchを10万回投げられる等)

他にも特典はたくさんあるがこのあたりが自分にとっては嬉しいポイントだった

Google Classrommの投稿をLINEに転送するには?

というわけでやっと本題。
学校側からはClassroomに登録したらアプリとかメールで投稿されたよ通知が飛ぶと説明されたものの、もしかするとうまく通知が機能しなかったり、確認が面倒な人もいるかもしれない(決して詩文ではないハズ‥)と思い、このようなものを作ろうということになった。

そもそも、どうやってClassroomの投稿をLINEに転送するのか?
今回は、以下のサービスを使用して実現させた。

  • Google App Script
  • LINE Message API
  • Classroom API
  • (webhook.site)

また、今回参考にさせていただいた(というよりほとんどソースを引用させていただきました)サイトはこちら

https://qiita.com/kashu02/items/1532c4b548e560eea21b

しかしこのサイトのソース通りでは自分の環境では正常に動作しなかったので少し手を加えました。

LINE Botを設定

まずはこちらのサイト(LINE Developers)でLINEアカウントにログイン、新規ボットを作成する。
このときの手順は記事にすることを考えてなかったので他の人の解説を見てもらうとして

ボットのセットアップが完了したら設定から

  • Webhook
  • 挨拶メッセージはオフ
  • グループに参加を許可
  • QRコードをLINEアプリで読み込んで友だち追加
  • シークレットIDを保存しておく
  • 好きに名前とかアイコンを変更

あたりを注意して行う。

その後、テストでWebhookの内容を見れるようにするサイト「Webhook.site」を開き、そこに表示されたWebhookURLをLINEDevのWebhookに入力して、Classroom Botにしたいグループに招待する。
するとそのときに、Webhook.siteのほうに新たにJSONが来ていると思うのでその中のグループIDをとっておく(後で使う

こんな感じでLINEのほうのセットアップは終了。

GASとかを設定

次はいよいよGoogle側の設定だ。

まずは、Classroomに所属した状態のGoogleアカウントにログインして、新しいスプレッドシートを作成する。
※このとき、たまに別のGoogleアカウントに戻ってしまうことが多いのでほかはログアウトなりしておくといいかも

上のツールバーの「ツール」→「スクリプトエディタ」の順に開くと、GASのエディタの画面が出てくる。

んで、ツールバーの「リソース」→「Googleの拡張サービス」を開いて、「Classroom API」の項目を探して、オンにする。

するとおそらく、まだ同意してないよねみたいなことが出てくるので、そのリンク先の項目に目を通して同意しておく。

このあと、エディタの画面に戻って、次のコードをコピペする。(コピペを嫌う人もいるけどまぁ読みやすいエディタなどで解読して覚えてもいいんじゃないかな)

  1. function myFunction() {
  2.   var lock = LockService.getUserLock(); //多重実行防止のためのロック
  3.   try {
  4.     lock.waitLock(25);
  5.     //****0時から6時まで処理スキップ******
  6.     var day = new Date();
  7.     var hour = day.getHours();
  8.     Logger.log(“day_hour:” + day + “_” + hour);
  9.     if(hour >=25 && hour <=26){
  10.       //return;
  11.     }
  12.     //*******************************
  13.     var optionalArgs = { //最大取得コース数
  14.       pageSize: 10
  15.     };
  16.     var courses = Classroom.Courses.list(optionalArgs).courses; //コースの取得
  17.     Logger.log(courses);
  18.     var i = 0;
  19.     for (var i = 0; i < courses.length; i++) { //コース別繰り返し処理
  20.       Logger.log(“i:” + i);
  21.       var course = courses[i];
  22.       var sheet = SpreadsheetApp.getActiveSheet();//シートOpen
  23.       var announcements = Classroom.Courses.Announcements.list(course.id).announcements;
  24.       var sheet_array = sheet.getDataRange().getValues(); //シートの配列への格納
  25.       Logger.log(“sheet_array:” + sheet_array);
  26.       if (announcements && announcements.length > 0) { //投稿件数>0のときの処理
  27.         Logger.log(“announcements.length:” + announcements.length);
  28.         if(announcements.length < 5){
  29.           var an_length = announcements.length; //投稿件数<5のとき取得は投稿件数
  30.         }
  31.         else {
  32.           var an_length = 5; //投稿件数>5のとき取得は5件まで
  33.         }
  34.         Classroom繰り返し処理:
  35.         for (var j = 0; j < an_length; j++) { //取得件数まで繰り返し(配列のため初期値0)
  36.           var announcemessage = []; //取得物格納用1次元配列の定義
  37.           var announcement = announcements[j];//投稿内容(JSON)
  38.           Logger.log(announcement);
  39.           var announcement_updateTimesheet = announcement.updateTime;//投稿(更新)時刻
  40.           var announcement_creatoruserId = creatoruId(announcement);//UserIdを名前に変換
  41.           var message_announcement = announcement.text;//投稿メッセージ
  42.           Logger.log(“sheet_array.length:” + sheet_array.length);
  43.           if(sheet_array.length != 1){
  44.             for (var sheet_j = sheet_array.length; sheet_j > 0; sheet_j–){//シートでの一致検索
  45.               Logger.log(“sheet_array[sheet_j – 1][2]:” + sheet_array[sheet_j – 1][2]);
  46.               if(sheet_array[sheet_j – 1][2] == message_announcement){//投稿メッセージがシートで発見したら次の投稿へContinue
  47.                 continue Classroom繰り返し処理;
  48.               }
  49.             }
  50.           }
  51.           var message_announcement_attachment = “”;
  52.           if (announcement.materials != undefined){ //添付物があるときの処理
  53.             if(announcement.materials[0].driveFile != undefined){
  54.               message_announcement_attachment = String.fromCharCode(10) + String.fromCharCode(10) +“*添付ファイル*” + String.fromCharCode(10) + announcement.materials[0].driveFile.driveFile.alternateLink;
  55.             } else
  56.               if(announcement.materials[0].link != undefined){
  57.                 message_announcement_attachment = String.fromCharCode(10) + “*リンク*” + String.fromCharCode(10) + announcement.materials[0].link.url;
  58.               } else if(announcement.materials[0].youtubeVideo != undefined){
  59.                 message_announcement_attachment = String.fromCharCode(10) +“*YouTubeVideo*” + String.fromCharCode(10) + announcement.materials[0].youtubeVideo.alternateLink;
  60.               } else if(announcement.materials[0].form != undefined){
  61.                 message_announcement_attachment = String.fromCharCode(10) +“*フォーム*” + String.fromCharCode(10) + announcement.materials[0].form.formUrl;
  62.               }
  63.             if(announcement.materials.length >= 2){ //添付物が2以上のとき
  64.               for(var k = 1; k < announcement.materials.length; k++){
  65.                 if(announcement.materials[k].driveFile != undefined){
  66.                   message_announcement_attachment = String.fromCharCode(10) + announcement.materials[k].driveFile.driveFile.alternateLink;
  67.                 } else
  68.                   if(announcement.materials[k].link != undefined){
  69.                     message_announcement_attachment = String.fromCharCode(10) + announcement.materials[k].link.url;
  70.                   } else if(announcement.materials[k].youtubeVideo != undefined){
  71.                     message_announcement_attachment = String.fromCharCode(10) + announcement.materials[k].youtubeVideo.alternateLink;
  72.                   } else if(announcement.materials[k].form != undefined){
  73.                     message_announcement_attachment = String.fromCharCode(10) + announcement.materials[k].form.formUrl;
  74.                   }
  75.               }
  76.             }
  77.           }
  78.           //***各項目の1次元配列の作成***
  79.           announcemessage.push(announcement_updateTimesheet);//投稿の作成(更新)時刻
  80.           announcemessage.push(announcement_creatoruserId);//投稿者
  81.           announcemessage.push(message_announcement);//投稿メッセージ
  82.           announcemessage.push(message_announcement_attachment);//投稿メッセージの添付
  83.           announcemessage.push(“sent”);//送信済フラグ
  84.           Logger.log(“j_announcemessage:” + j +“_” + announcemessage);
  85.           //*******
  86.           var message = announcemessage[1] + String.fromCharCode(10) + String.fromCharCode(10) + announcemessage[2] + announcemessage[3];//送信メッセージの作成
  87.           sheet_sent(message)//送信処理
  88.           sheet.appendRow(announcemessage);//シート配列への追加
  89.         }
  90.         //—————-
  91.           Logger.log(“j_sheet_array:” + j +“_” + sheet_array);
  92.           sheet.sort(1);//シートの並び替え
  93.       }
  94.     }
  95.   }
  96.   catch(e) {
  97.   Logger.log(e);
  98. } finally {
  99.   lock.releaseLock();//ロック解除
  100. }
  101. return;
  102. }
  103. function creatoruId(announcement){
  104.   var announcement_creatoruserId;
  105.   switch (announcement.creatorUserId){
  106.     case announcement.creatorUserId = “{ユーザーID1}”://?それぞれcaseで処理
  107.       announcement_creatoruserId = “{1さんの表示名}”;
  108.        break;
  109.   }
  110.   if (announcement_creatoruserId == undefined){
  111.     announcement_creatoruserId = “名無しさん”;
  112.   }
  113.   return(announcement_creatoruserId);
  114. }
  115. function sheet_sent(message){
  116.   var CHANNEL_ACCESS_TOKEN = ‘{チャンネルアクセストークンを入力}’; //?チャンネルアクセストークンを入力する
  117.   var group_ID = ‘{グループIDを入力}’; //?グループIDを入力
  118.   var postData = {
  119.     “to” : group_ID,
  120.     “messages”: [{
  121.       “type”: “text”,
  122.       “text”: message,
  123.     }],
  124.     “notificationDisabled” : “false”,
  125.   };
  126.   var url = “https://api.line.me/v2/bot/message/push”;
  127.   var headers = {
  128.     “Content-Type”: “application/json”,
  129.     ‘Authorization’: ‘Bearer ‘ + CHANNEL_ACCESS_TOKEN,
  130.   };
  131.   var options = {
  132.     “method”: “post”,
  133.     “headers”: headers,
  134.     “payload”: JSON.stringify(postData)
  135.   };
  136.   var response = UrlFetchApp.fetch(url, options);
  137.   return;
  138.   // 実際のフォルダとスプレッドシート情報を比較。
  139.   var updateFolderMap = [];
  140.   for (key in lastUpdateMap) {
  141.     if( UPDATE_SHEET_ID == lastUpdateMap[key].fileId ){
  142.       continue;
  143.     }
  144.     if(key in sheetData) {
  145.       // フォルダ名がシートに存在する場合。
  146.       if(lastUpdateMap[key].lastUpdate > sheetData[key].lastUpdate) {
  147.         // フォルダが更新されている場合。
  148.         sheet.getRange(sheetData[key].rowNo, 2).setValue(lastUpdateMap[key].lastUpdate);
  149.         sheet.getRange(sheetData[key].rowNo, 3).setValue(lastUpdateMap[key].fileId);
  150.         updateFolderMap.push({filename:key, lastUpdate:lastUpdateMap[key].lastUpdate, fileId:lastUpdateMap[key].fileId});
  151.       }
  152.     } else {
  153.       // フォルダ名がシートに存在しない場合。
  154.       var newRow = sheet.getLastRow() + 1;
  155.       sheet.getRange(newRow, 1).setValue(key);
  156.       sheet.getRange(newRow, 2).setValue(lastUpdateMap[key].lastUpdate);
  157.       sheet.getRange(newRow, 3).setValue(lastUpdateMap[key].fileId);
  158.       updateFolderMap.push({filename:key, lastUpdate:lastUpdateMap[key].lastUpdate, fileId:lastUpdateMap[key].fileId});
  159.     }
  160.   }
  161. }

 

だいぶ汚いコードだったり、メール作らないのに残骸が残ってたりしますがそのあたりはご勘弁を。

①に上で取得した「チャンネルアクセストークン」を”内にコピペ
②に上で取得したグループIDをコピペ

これでとりあえずツールバーあたりの▶マークを押してmyfunctionを実行。色々と権限の許可を要求されるので許可して、Classroomに上がっている内容がLINEにとんでくれば成功。

ただ、名前欄が数字の文字列だと思うので、③のところにユーザーIDと名前をcaseで指定していく。

スプレッドシートのほうに追記されている部分があると思うのでそれらをすべて削除してから再実行すれば何度でもテストできる。

これでひとまずは手動で実行可能になったので、🕘みたいなマークからトリガーを5分おきくらいに設定してあとは放置。

G Suiteなので1分おきとかでも大丈夫だとは思うけどまぁ念の為5分おきで自分は設定している。

 

参考にしたサイトでは、グループに投稿することができなかったり、取得の繰り返し処理ができなかったりと不便だったので載せてみました。
LINE Devが面倒だったりする場合はLINE Notify APIとかでもいいかもしれない。
あとは、Webhookで投稿できるようなDiscordとかSlackとかでもちょっと改変すればできそう

それではまたいつか

-ソフトウェア, 趣味
-, , , ,

執筆者:


  1. え= より:

    コード実行したら

    構文エラー: SyntaxError: Invalid or unexpected token 行: 8 ファイル: コード.gs

    ってエラーでました…

  2. え= より:

    上は”や’を”,’に直すことで解決しましたが、

    Exception: Request failed for https://api.line.me returned code 400. Truncated server response: {“message”:”The property, ‘to’, in the request body is invalid (line: -, column: -)”} (use muteHttpExceptions option to examine full response)

    っていうエラーがでます…

comment

メールアドレスが公開されることはありません。

関連記事

no image

SONY製ヘッドホン WH-CH510を購入

こんにちは。takaremonnです。 最近、世間では新型コロナウイルスが騒がれていて、電車に乗る機会が多い自分も気にし始めた。 さて、手頃でそれなりの性能のあるヘッドホン無いかな~ と探していたとこ …

no image

Xiaomi Mi Band 4 を発表

こんばんは takaremonnです ちょうど学校のテストが終わり、一休み(2週間後にはまたテスト)   そんな中、いつものようにRSSリーダー(Inoreader)を巡回していると、個人的 …

no image

Braveクリエイターに登録した

こんにちは takaremonnです。 最近はコロナウイルスの影響で自宅に引きこもる生活が続いていて少し物足りない気がする。 目次1 「Brave」とは2 「Braveクリエイター」登録手順3 広告を …

no image

Googleドライブの更新をLINEで通知したい

こんばんわ、takaremonnです。 最近はコロナウイルスによって休みが多くなってきていて、ここに何か書こうかなとしばしば思うのだけれど、なかなか書こうという気になれない。ただ、サーバーを移したり、 …