2
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

文系でも分かる、GASによる授業通知のLINE bot の作成 その④ 〜setTriggerを使って、指定の時間にプッシュ通知を送る〜

Last updated at Posted at 2020-11-19

文系でも分かる、GASによる授業通知のLINE bot の作成 その③の続きです。
今回で最後になります。

  1. 文系でも分かる、GASによる授業通知のLINE bot の作成 その① 〜GASを使ったLINE botの作成〜
  2. 文系でも分かる、GASによる授業通知のLINE bot の作成 その② 〜GASとスプレッドシートの連携、日付のフォーマット〜
  3. 文系でも分かる、GASによる授業通知のLINE bot の作成 その③
  4. 文系でも分かる、GASによる授業通知のLINE bot の作成 その④ 〜setTriggerを使って、指定の時間にプッシュ通知を送る〜 ←今回

setTriggerというものを使い、授業開始10分前に通知が来るようにします。

#この記事でわかること

  • GASのsetTrigger の使い方
  • Messaging APIのプッシュ通知の送り方

#今回やること

  • 要件④授業の10分前に授業を通知する機能の追加

Screen Shot 2020-11-19 at 22.15.24.png

#要件定義

  • push通知を送るfunctionの作成
  • push通知で送る授業情報を取得するfunctionを作成
  • 上のfunctionが指定の時間に実行されるためのfunctionを作成

push通知を送るfunctionの作成

Code.gs
function push(text, zoom) {
  //メッセージを送信(push)する時に必要なurlでこれは、皆同じなので、修正する必要ありません。
  //この関数は全て基本コピペで大丈夫です。
  var url = "https://api.line.me/v2/bot/message/push";
  var headers = {
    "Content-Type" : "application/json; charset=UTF-8",
    'Authorization': 'Bearer ' + access_token,
  };
  
  //toのところにメッセージを送信したいユーザーのIDを指定します。(toは最初の方で自分のIDを指定したので、linebotから自分に送信されることになります。)
  //textの部分は、送信されるメッセージが入ります。createMessageという関数で定義したメッセージがここに入ります。
  var postData = {
    "to" : user_id,
    "messages" : [
      {
        'type':'text',
        'text':text,
      },
      {
        'type':'text',
        'text':zoom,
      }
    ]
  };
  
  var options = {
    "method" : "post",
    "headers" : headers,
    "payload" : JSON.stringify(postData)
  };
  
  return UrlFetchApp.fetch(url, options);
}

"messages" : [{}{}] の形にすることで、パスワードが別のメッセージとして送られてくるようにしています(上の画像参照)。こうすることで、パスワードのコピーが容易になっています。

#push通知で送る授業情報を取得するfunctionを作成

Code.gs
function pushClassInfo() {
 //function findNextClassを実行
  var classInfos = findNextClass();
  var today = new Date();
  //あとで、現在より15分後の日時を取得します。
  var quarterAfter = new Date();
  var day = today.getDay();
  var array = ["日", "月", "火", "水", "木", "金", "土"];
 //quarterAfterに入っている日時が、現在より15分後になりました。
  quarterAfter.setMinutes(quarterAfter.getMinutes() + 15);
  var hhmmToday = Utilities.formatDate( today, 'Asia/Tokyo', 'HH:mm');
  var hhmmQuarter = Utilities.formatDate( quarterAfter, 'Asia/Tokyo', 'HH:mm');
  console.log(classInfos);
  console.log(classInfos.startTime <= hhmmQuarter); //授業が始まるのは、今から15分後より前、つまりもうすぐ授業が始まる。
 //findNextClassで取得した授業が現在の曜日のものか
  if(classInfos.classDay == array[day] && classInfos.startTime <= hhmmQuarter ){ 
    var message = "もうすぐ次の授業です。\n" + classInfos.classDay + "曜" + classInfos.classNum + "限 (" + classInfos.startTime + "-" + classInfos.endTime + 
      ")\n授業名:" + classInfos.className + 
        "\nZoomID:" + classInfos.zoomID + 
          "\nPass:" + classInfos.zoomPass;
    console.log(message);
   //function pushを実行
    push(message, classInfos.zoomPass);
  } else {console.log("not upcoming one ");}
}

流れとしては、fuction findNextClassで1番近い授業を取得して、それが始まるのが今から15分以内かどうか判別し、trueならクラスの情報をpushするという感じです。

#指定時間に通知が送られてくるようにする

コード(コピペで動きます)

Code.gs
//授業時間が設定されている時、その授業時間の10分前にpushClassInfoを実行するタイマーをセットする
function setTrigger(){
  var today = new Date();
  var year = today.getFullYear();
  var month = today.getMonth();
  var date = today.getDate();
  for(let i=3; i <= 27; i+=4) {
    if(sheet.getRange(i, 1).getValue()){
      var classStart = sheet.getRange(i, 1).getValue();
      var startMinutes = classStart.getMinutes();
      classStart.setFullYear(year);
      classStart.setMonth(month);
      classStart.setDate(date);
      classStart.setMinutes(startMinutes - 10);
      console.log(classStart);
      ScriptApp.newTrigger('pushClassInfo').timeBased().at(classStart).create();
    }
  }
}

function delTrigger() {
  var triggers = ScriptApp.getProjectTriggers();
  for(let i=0; i < triggers.length; i++) {
    if (triggers[i].getHandlerFunction() == "pushClassInfo") {
      ScriptApp.deleteTrigger(triggers[i]);
    }
  }
}

###トリガーの使い方
ScriptApp.newTrigger('function名').timeBased().at(時間).create()
でトリガーというものをセットすることがでます。このトリガーによって、指定した日時にfunctionを実行させることができます。
ただ、セットしたトリガーは残り続けてしまうので、 deleleTriggerで削除します。
あとは、function setTriggerを早朝に、delTriggerを深夜に実行する設定をすれば完了です。

#setTriggerとdelTriggerを毎日呼び出す
https://tonari-it.com/gas-trigger-set/#toc4
こちらの記事の、「5. 毎日指定の時刻のDateオブジェクトを作成する」を参考に、setTriggerを早朝(1限より前の時間)に、delTriggerを深夜(7限よりあとの時間)にセットしてください。つまり、記事の作業を2回おこないます。
ちなみに1〜4もScriptApp.newTriggerについて詳しく解説してくれているので、読むと勉強になります。

これで完成です!
最後に更新を忘れないようにしましょう。

#最終的なコードの全体像
全て書き終えた後のコードは以下のようになります

Code.gs
var access_token = "アクセストークン"
// 自分のユーザーIDを指定します。LINE Developersの「Your user ID」の部分です。
var user_id = "ユーザーID"

//★★スプレッドシートID★★
var ss = SpreadsheetApp.openById("スプレッドシートID");
//★★シート名★★
var sheet = ss.getSheetByName("シート名");

function doPost(e) {
  var event = JSON.parse(e.postData.contents).events[0];
  var returnMessage = "曜日と時限(半角:1〜7)を指定してね!\n次の授業が知りたいときは、「次」と入力してね!";
  if(event.source.userId == user_id){
    //返信するためのトークン取得
    var reply_token= event.replyToken;
    if (typeof reply_token === 'undefined') {
      return;
    }
    var message = event.message.text;
    for(let i=3; i<=7; i ++) {
      var dateColumn = i;
      var day = sheet.getRange(1, i).getValue();
      if(message.includes(day)){
        for(let j=2; j<=26; j += 4) {
          var classNumRow = j;
          var classNum = sheet.getRange(j, 1).getValue();
          if(message.includes(classNum) && sheet.getRange(classNumRow, dateColumn).getValue()){
            var classInfos = getClassInfo(classNumRow, dateColumn);
            var returnMessage = classInfos.classDay + "曜" + classInfos.classNum + "限 (" + classInfos.startTime + "-" + classInfos.endTime + 
              ")\n授業名:" + classInfos.className + 
                "\nZoomID: " + classInfos.zoomID + 
                  "\nPass: " + classInfos.zoomPass;
            reply(reply_token, returnMessage);
          } else if(message.includes(classNum) && !sheet.getRange(classNumRow, dateColumn).getValue()) {
            var returnMessage = "授業はありません。"
            reply(reply_token, returnMessage);
          }
        }
      }
    }
    
    if(message.includes("次")){
      var classInfos = findNextClass();
      var returnMessage = classInfos.classDay + "曜" + classInfos.classNum + "限 (" + classInfos.startTime + "-" + classInfos.endTime + 
        ")\n授業名:" + classInfos.className + 
          "\nZoomID: " + classInfos.zoomID + 
            "\nPass: " + classInfos.zoomPass;
      reply(reply_token, returnMessage);
    } else {reply(reply_token, returnMessage);}
  }
}

function reply(reply_token, returnMessage) {
  var reply_url = 'https://api.line.me/v2/bot/message/reply';

  // メッセージを返信 
  UrlFetchApp.fetch(reply_url, {
    'headers': {
      'Content-Type': 'application/json; charset=UTF-8',
      'Authorization': 'Bearer ' + access_token,
    },
    'method': 'post',
    'payload': JSON.stringify({
      'replyToken': reply_token,
      'messages': [{
        'type': 'text',
        'text': returnMessage,
      }],
    }),
  });
  return ContentService.createTextOutput(JSON.stringify({'content': 'post ok'})).setMimeType(ContentService.MimeType.JSON);
}


function push(text, zoom) {
  //メッセージを送信(push)する時に必要なurlでこれは、皆同じなので、修正する必要ありません。
  //この関数は全て基本コピペで大丈夫です。
  var url = "https://api.line.me/v2/bot/message/push";
  var headers = {
    "Content-Type" : "application/json; charset=UTF-8",
    'Authorization': 'Bearer ' + access_token,
  };
  
  //toのところにメッセージを送信したいユーザーのIDを指定します。(toは最初の方で自分のIDを指定したので、linebotから自分に送信されることになります。)
  //textの部分は、送信されるメッセージが入ります。createMessageという関数で定義したメッセージがここに入ります。
  var postData = {
    "to" : user_id,
    "messages" : [
      {
        'type':'text',
        'text':text,
      },
      {
        'type':'text',
        'text':zoom,
      }
    ]
  };
  
  var options = {
    "method" : "post",
    "headers" : headers,
    "payload" : JSON.stringify(postData)
  };
  
  return UrlFetchApp.fetch(url, options);
}

function pushClassInfo() {
  var classInfos = findNextClass();
  var today = new Date();
  var quarterAfter = new Date();
  var day = today.getDay();
  var array = ["日", "月", "火", "水", "木", "金", "土"];
  quarterAfter.setMinutes(quarterAfter.getMinutes() + 15);
  var hhmmToday = Utilities.formatDate( today, 'Asia/Tokyo', 'HH:mm');
  var hhmmQuarter = Utilities.formatDate( quarterAfter, 'Asia/Tokyo', 'HH:mm');
  console.log(classInfos);
  console.log(classInfos.startTime <= hhmmQuarter); //授業が始まるのは、今から15分後より前、つまりもうすぐ授業が始まる。
  if(classInfos.classDay == array[day] && classInfos.startTime <= hhmmQuarter ){ 
    var message = "もうすぐ次の授業です。\n" + classInfos.classDay + "曜" + classInfos.classNum + "限 (" + classInfos.startTime + "-" + classInfos.endTime + 
      ")\n授業名:" + classInfos.className + 
        "\nZoomID:" + classInfos.zoomID + 
          "\nPass:" + classInfos.zoomPass;
    console.log(message);
    push(message, classInfos.zoomPass);
  } else {console.log("not upcoming one ");}
}

function findNextClass() {
  var today = new Date();
  var hour = today.getHours();
  var minutes = today.getMinutes();
  console.log(today);
  for(let i=0; i <= 6; i++) {
    var day = (today.getDay() + i) % 7;
    var dateColumn = day + 3;
    var array = ['日','月','火','水','木', '金', '土'];
    console.log("曜日:" + array[day]);
    
    var searchHour = 0;
    if(day == today.getDay()){searchHour = hour;} else { searchHour = 6;}
    for(var j=0; j < 24-searchHour; j++) {
      console.log(searchHour + "時");
      for(let k=2; k <= 26; k+=4){
        var classNumRow = k;
        var startTimeRow = k + 1;  
        //始業時間が設定されている場合、始業時間を取得
        if(sheet.getRange(startTimeRow, 1).getValue()) {
          var startTime = sheet.getRange(startTimeRow, 1).getValue();
          var startHour = startTime.getHours();
          var startMinutes = startTime.getMinutes();
          console.log("start hour: " + startHour);
          //検索した時、今と検索時間の日付と時間が一致していても、今の分が始業の分を超えている場合は情報を取得しない          
          if(searchHour === startHour && today.getDay() === day && hour === searchHour && minutes > startMinutes){
            console.log("\nalready orver\n");
            //時間が一致し、授業が存在する場合、情報を取得
          } else if(searchHour === startHour && sheet.getRange(classNumRow, dateColumn).getValue()){
            var classInfos = getClassInfo(classNumRow, dateColumn);
            return classInfos;
          }
        }
      }
      searchHour += 1;
    }
  }  
}

function getClassInfo(rowNum, dateColumn){
  var className = sheet.getRange(rowNum, dateColumn).getValue();
  var classDay = sheet.getRange(1, dateColumn).getValue();
  var classNum = sheet.getRange(rowNum, 1).getValue();
  var startTime = Utilities.formatDate( sheet.getRange(rowNum + 1, 1).getValue(), 'Asia/Tokyo', 'HH:mm');
  var endTime = Utilities.formatDate( sheet.getRange(rowNum + 3, 1).getValue(), 'Asia/Tokyo', 'HH:mm');
  var zoomID = sheet.getRange(rowNum + 1, dateColumn).getValue();
  var zoomPass = sheet.getRange(rowNum + 2, dateColumn).getValue();
  var classInfos = {className: className, classDay: classDay, classNum: classNum, startTime: startTime, endTime: endTime, zoomID: zoomID, zoomPass: zoomPass};
  return classInfos;
}

function setTrigger(){
  var today = new Date();
  var year = today.getFullYear();
  var month = today.getMonth();
  var date = today.getDate();
  for(let i=3; i <= 27; i+=4) {
    if(sheet.getRange(i, 1).getValue()){
      var classStart = sheet.getRange(i, 1).getValue();
      var startMinutes = classStart.getMinutes();
      classStart.setFullYear(year);
      classStart.setMonth(month);
      classStart.setDate(date);
      classStart.setMinutes(startMinutes - 10);
      console.log(classStart);
      ScriptApp.newTrigger('pushClassInfo').timeBased().at(classStart).create();
    }
  }
}

function delTrigger() {
  var triggers = ScriptApp.getProjectTriggers();
  for(let i=0; i < triggers.length; i++) {
    if (triggers[i].getHandlerFunction() == "pushClassInfo") {
      ScriptApp.deleteTrigger(triggers[i]);
    }
  }
}

#最後に
いかがだったでしょうか?
僕はこれを作った時、文系でもこんなものが作れるんだと感動しました。
文系目線でなるべくわかりやすく解説を入れたつもりなので、少しでも役に立てば嬉しいです。
なお、未経験文系大学生が書いているので、誤り等あるかもしれませんがご了承ください。
最後まで取り組んでくださってありがとうございました!

2
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?