素人が翌日の勤務(シフト)を告げるLINEbotを作った話【7】

トリガー設定

 前回、①特定の日付の勤務情報を取得する関数と②①に翌日の日付を渡し、返ってきた勤務情報をLINEに送信する関数を書きました。

 ②の関数を実行するとLINEにメッセージが届くのですが、毎日自動で送信してほしいところですよね。

 そこで使うのが、トリガー設定です。

無題

 スクリプトエディタ画面の時計マーク(赤枠)を選択すると、下の画面が表示されます。

無題

 右下の「トリガーを追加」を押すと設定画面に移ります。

無題

 時刻以外の設定は上の画像と同じにしてください。時刻は会社の事情に合わせて決めると良いと思います。私の所属する会社の場合、翌日の勤務を知らせるため、夜7~8時に設定しています(上の画像は記事を書く都合上13~14時にしています)。夕食が終わって落ち着いた頃に通知が来る、という狙いがあります。夜だと迷惑がかからないか心配であれば、夕方のうちに、あるいは当日の朝に通知する方法もあります(当日となると関数も書き換えなければいけませんが)。

 それでは、実際に送られてくるか、設定時刻まで待ってみましょう。

 ・・・来た!

画像6

 ちゃんと届きましたね!これで「翌日の勤務(シフト)を告げるLINEbot」がひとまずできあがったしたことになります。おめでとうございます!

これで完成か?否!

 勤務の情報を毎日送るという、このbotの主たる機能は組み上がりました。しかし実際に使ってみると、まだまだ細かい部分の調整ができていないことがわかります。ガバガバです。例えば・・・

画像4

 勤務帯の項目

 11/06の例のように、特定の勤務帯が0人の日があると、メッセージにはその部分が空欄になって出てきます。これくらいならまあ良いでしょう。では、11/07の場合はどうでしょう。土日で休みなので、全員休みの欄に入ってますね。こうなるとなんかちょっと間抜けに見えます。それぞれの勤務帯が0人の場合は、勤務帯の項目も消した方がスマートです。

 休みの日はメッセージいらなくない?

 地味に重要なポイントなのですが、こういうツールはユーザーが不快に感じないことが大切だと思うのです。例えば金曜日の夜。明日は休みという場面で上の画像の11/07のメッセージが送られてきたら、「知ってるわ!」ってなりませんか?勤務帯の項目の話ともかぶりますが、いらない情報は省いたほうが良いと思うのです。

コードを改良していきます

 まず、不要な勤務帯が消えるようにコードを書き換えます。関数”getWorkDataFromSheet”を以下のコードに書き換えてください。

function getWorkDataFromSheet(targetDate){ 
 const week = ["日","月","火","水","木","金","土"];
 const lastRow = sheet.getLastRow();
 const month = Utilities.formatDate(targetDate, 'JST', 'MM');
 const day = Utilities.formatDate(targetDate, 'JST', 'dd');
 const date = month + "/" + day 
 const dateFinder = sheet.createTextFinder(date).findAll();
 
   for ( var i = 0; i < dateFinder.length; i++ ) {
       var dateColumn = dateFinder[i].getColumn();
   }
 
 const data = sheet.getRange(1,dateColumn,lastRow);
 let earlyTurnList = [];
 let dayShiftList = [];
 let lateTurnList =[];
 let nightShiftList = [];
 let afterNightShiftList = [];
 let holidayList = [];
 let paidVacationList = [];
 
 
 let earlyTurnFinder = data.createTextFinder('早').findAll();
 for ( let i = 0; i < earlyTurnFinder.length; i++ ) {
   let earlyTurn = earlyTurnFinder[i].offset( 0 , - dateColumn + 1).getValue();
   earlyTurnList.push(earlyTurn)
  }
 
 let dayShiftFinder = data.createTextFinder('日').findAll();
 for ( let i = 0; i < dayShiftFinder.length; i++ ) {
   let dayShift = dayShiftFinder[i].offset( 0 , - dateColumn + 1).getValue();
   dayShiftList.push(dayShift)
  }  
 
 let lateTurnFinder = data.createTextFinder('遅').findAll();
 for ( let i = 0; i < lateTurnFinder.length; i++ ) {
   let lateTurn = lateTurnFinder[i].offset( 0 , - dateColumn + 1).getValue();
   lateTurnList.push(lateTurn)
  } 
 
 let nightShiftFinder = data.createTextFinder('夜').findAll();
 for ( let i = 0; i < nightShiftFinder.length; i++ ) {
   let nightShift = nightShiftFinder[i].offset( 0 , - dateColumn + 1).getValue();
   nightShiftList.push(nightShift)
  }
 
 let afterNightShiftFinder = data.createTextFinder('明').findAll();
 for ( let i = 0; i < afterNightShiftFinder.length; i++ ) {
   let afterNightShift = afterNightShiftFinder[i].offset( 0 , - dateColumn + 1).getValue();
   afterNightShiftList.push(afterNightShift)
  }
 
 let holidayFinder = data.createTextFinder('公').findAll();
 for ( let i = 0; i < holidayFinder.length; i++ ) {
   let holiday = holidayFinder[i].offset( 0 , - dateColumn + 1).getValue();
   holidayList.push(holiday)
  }
 
 let paidVacationFinder = data.createTextFinder('有').findAll();
 for ( let i = 0; i < paidVacationFinder.length; i++ ) {
   let paidVacation = paidVacationFinder[i].offset( 0 , - dateColumn + 1).getValue();
   paidVacationList.push(paidVacation)
  }

 var text = date +"("+week[targetDate.getDay()]+") 勤務者一覧"+"\n\n" 
 if(earlyTurnList.length > 0){
   text = text +  "早番 : " + earlyTurnList +"\n\n"
 }  
 if(dayShiftList.length > 0){
   text = text +  "日勤 : " + dayShiftList +"\n\n"
 }  
 if(lateTurnList.length > 0){
   text = text +  "遅番 : " + lateTurnList +"\n\n"
 }
 if(nightShiftList.length > 0){
   text = text +  "夜勤 : " + nightShiftList +"\n\n"
 }
 if(afterNightShiftList.length > 0){
   text = text +  "明け : " + afterNightShiftList +"\n\n"
 }  
 if(holidayList.length + paidVacationList.length > 0){
  text = text +  "公・休・有 : \n" + holidayList+ "," + paidVacationList + "\n\n" ;
  }
    
 return text;
}

 変わった部分は下部のテキスト成形部分で、

var text = date +"("+week[targetDate.getDay()]+") 勤務者一覧"+"\n\n" +
    "早番 : " + earlyTurnList +"\n\n" +
    "日勤 : " + dayShiftList +"\n\n"  +
    "遅番 : " + lateTurnList + "\n\n" +
    "夜勤 : " + nightShiftList +"\n\n"+
    "明け : " + afterNightShiftList +"\n\n";


if(holidayList.length + paidVacationList.length > 0){
 text = text +  "公・休・有 : \n" + holidayList+ "," + paidVacationList + "\n\n" ;
 }

 これを

var text = date +"("+week[targetDate.getDay()]+") 勤務者一覧"+"\n\n" 
 if(earlyTurnList.length > 0){
   text = text +  "早番 : " + earlyTurnList +"\n\n"
 }  
 if(dayShiftList.length > 0){
   text = text +  "日勤 : " + dayShiftList +"\n\n"
 }  
 if(lateTurnList.length > 0){
   text = text +  "遅番 : " + lateTurnList +"\n\n"
 }
 if(nightShiftList.length > 0){
   text = text +  "夜勤 : " + nightShiftList +"\n\n"
 }
 if(afterNightShiftList.length > 0){
   text = text +  "明け : " + afterNightShiftList +"\n\n"
 }  
 if(holidayList.length + paidVacationList.length > 0){
   text = text +  "公・休・有 : \n" + holidayList+ "," + paidVacationList + "\n\n" ;
  }

 このように書き換えました。if文で勤務者が0だと項目自体が表示されないようにしています。

 次に、休みの日の分はメッセージを送らないようにしたいのですが、これをどのようにコードに落とし込むか。

 現在、関数”getWorkDataFromSheet”で取得したデータを"sendTomorrowsShiftEverynight"に渡して(返り値)LINEに送っています。出勤者の人数は”getWorkDataFromSheet”で計算していますが、LINEに送るか判断をする役割は"sendTomorrowsShiftEverynight"に持たせたいと思っています。”getWorkDataFromSheet”は純粋にデータからメッセージ成形の役割だけを担ってほしいのです。これには理由があって、のちのち、ユーザーが特定の日付を入力したら、その日の勤務データが返ってくる仕組みを付け足したいと思っていて、そのときにも”getWorkDataFromSheet”を使います。複数の使い道がある関数は、予期せぬバグがでないように汎用性の高いコードにしておきたいのです。したがって、送信するかしないかの判断は"sendTomorrowsShiftEverynight"の方にさせるということになります。

 さてここで何が問題になるかというと、関数”getWorkDataFromSheet”の方では、勤務者の人数が変数に入っているのでそのまま使えるのですが、その人数を"sendTomorrowsShiftEverynight"で使うとなると、少し工夫が必要になります。まず、”getWorkDataFromSheet”の最終行、

return text

ここを以下のように書き換えます。

if(dayShiftList.length + earlyTurnList.length + lateTurnList.length + nightShiftList.length + afterNightShiftList.length < 1 ){
   var workers = 0; 
 }
 
 return  {text: text, workers: workers}; 

 これまで、text という変数に勤務者の情報を入れて返していましたが、workersという変数に勤務者の人数を入れて一緒に返すようにしました。

 次に、"sendTomorrowsShiftEverynight"の

function sendTomorrowsShiftEverynight(){
 var today = new Date();
 today.setDate(today.getDate()+1)
 const text = getWorkDataFromSheet(today)
 
 
 sendLineMessageUsingBroadcast(text);
}

 これを以下のように書き換えます。

function sendTomorrowsShiftEverynight(){
 var today = new Date();
 today.setDate(today.getDate()+1)
 const obj = getWorkDataFromSheet(today)
 const text = obj.text
 if(obj.workers == "0" ){
   return;  
 }
 sendLineMessageUsingBroadcast(text);
}

 ”getWorkDataFromSheet”から返されるデータが2つになるのに合わせて、受け取ったデータをそれぞれ変数に収めます。workersが0なら、送信する前にreturn(何もしない)し、0じゃないなら”sendLineMessageUsingBroadcast”でメッセージを送ります。

 これで「出勤者が一人もいない日=休日」の分のメッセージは送られないことになりました。土日休みの会社の場合、日~木は送信され、金曜日と土曜日は送信されないことになります。

テストしてみましょう

スクリプトエディタの上部から"sendTomorrowsShiftEverynight"を選び、実行してみてください。今日が日~木曜日であればLINEにメッセージが送られてくるはずです。今日が金・土曜日だと送られてきません。またスプレッドシート上で勤務者を外して送信すると、以下のように不要な項目は表示されなくなりました。(今回は夜勤・明けを日勤に回しました)

画像5

ちなみに”sendLineMessageUsingBroadcast”の3行目

today.setDate(today.getDate()+1)

の+1部分は○日後という意味なので、2日後のメッセージが欲しければ+2、1週間後なら+7などと変えられます。平日と土日の挙動の違いを試したいときはここで調整してください。(テストが終わったら戻すように気をつけてください)

ここまでのコード

const channel_access_token = "トークン";
const headers = {
  "Content-Type": "application/json; charset=UTF-8",
  "Authorization": "Bearer " + channel_access_token
};
const spreadsheet = SpreadsheetApp.openById("ID");
const sheet = spreadsheet.getSheetByName("勤務表");
const userSheet = spreadsheet.getSheetByName("ユーザー");
const logSheet = spreadsheet.getSheetByName("ログ");


function doPost(e) {
 const webhookData = JSON.parse(e.postData.contents).events[0];
 const eventType = webhookData.type;
 const replyToken = webhookData.replyToken;

 switch(eventType){
   case "follow":  //友だち追加
     follow(webhookData);
     break;
   case "message":  //メッセージ受信
     messageEvent(webhookData);
     break;
   case "unfollow":  //ブロック
     unfollow(webhookData)
     break;
   
 }
}

function follow(webhookData){
 const userId = webhookData.source.userId;
 const nickname = getUserProfile(userId);
 const last_row = userSheet.getLastRow();

 userSheet.getRange(last_row+1,2).setValue(nickname);
 userSheet.getRange(last_row+1,3).setValue(userId);
 userSheet.getDataRange().removeDuplicates([3])  
}


function messageEvent(webhookData){
 const userId = webhookData.source.userId;
 const nickname = getUserProfile(userId);
 const timestamp = new Date(webhookData.timestamp)
 const message = webhookData.message.text.split("\n");
 const replyToken = webhookData.replyToken;
 const whatToDo = message[0];
 const name = message[1];
 const date = message[2];
 const newshift = message[3];
 const last_row = userSheet.getLastRow();
 const lastRow = logSheet.getLastRow();

 logSheet.getRange(lastRow+1,1).setValue(timestamp);
 logSheet.getRange(lastRow+1,2).setValue(nickname);
 logSheet.getRange(lastRow+1,3).setValue(userId);
 logSheet.getRange(lastRow+1,4).setValue(message[0]);
 logSheet.getRange(lastRow+1,5).setValue(message[1]);
 logSheet.getRange(lastRow+1,6).setValue(message[2]);
 logSheet.getRange(lastRow+1,7).setValue(message[3]);

 switch (whatToDo) {
    case 'こんにちは':
      sendLineMessageFromReplyToken(replyToken, "さようなら");
      break;
    default:
      return;
      break;  
}
}

function unfollow(webhookData){
 const data = userSheet.getDataRange()
 const userId = webhookData.source.userId;
 const userFinder = data.createTextFinder(userId).findAll();
 for ( var i = 0; i < userFinder.length; i++ ) {
       var userRow = userFinder[i].getRow();
       var user = userFinder[i].offset(0,1).getValue()
   userSheet.deleteRows(userRow);
 }
}

function getUserProfile(userId){ 
 const Url = 'https://api.line.me/v2/bot/profile/' + userId;
 const userProfile = UrlFetchApp.fetch(Url,{
   'headers': {
     'Authorization' :  'Bearer ' + channel_access_token,
   },
 })
 return JSON.parse(userProfile).displayName;
}

function sendLineMessageFromReplyToken(token, replyText) {
var url = "https://api.line.me/v2/bot/message/reply";
var headers = {
 "Content-Type": "application/json; charset=UTF-8",
 "Authorization": "Bearer " + channel_access_token
};
var postData = {
 "replyToken": token,
 "messages": [{
   "type": "text",
   "text": replyText
 }]
};
var options = {
 "method": "POST",
 "headers": headers,
 "payload": JSON.stringify(postData)
};
return UrlFetchApp.fetch(url, options);
}

function sendLineMessageUsingBroadcast(text) {
var Url = "https://api.line.me/v2/bot/message/broadcast";
var postData = {
   "messages": [{
   "type": "text",
   "text":  text,
 }
             ]};
var options = {
 "method": "POST",
 "headers": headers,
 "payload": JSON.stringify(postData)
};
return UrlFetchApp.fetch(Url, options);
}

function sendLineMessageFromUserId(text,userId) {
var url = "https://api.line.me/v2/bot/message/push";
var postData = {
 "to": userId,
 "messages": [{
   "type": "text",
   "text":  text,
 }]
};
var options = {
 "method": "POST",
 "headers": headers,
 "payload": JSON.stringify(postData)
};
return UrlFetchApp.fetch(url, options);
}

function sendLineMessageToAdmin(text) {
var url = "https://api.line.me/v2/bot/message/push";
var postData = {
 "to": "ユーザーID",
 "messages": [{
   "type": "text",
   "text":  text,
 }]
};
var options = {
 "method": "POST",
 "headers": headers,
 "payload": JSON.stringify(postData)
};
return UrlFetchApp.fetch(url, options);
}

function sendTomorrowsShiftEverynight(){
 var today = new Date();
 today.setDate(today.getDate()+1)
 const obj = getWorkDataFromSheet(today)
 const text = obj.text
 if(obj.workers == "0" ){
   return;  
 }
 sendLineMessageUsingBroadcast(text);
}

function getWorkDataFromSheet(targetDate){ 
 const week = ["日","月","火","水","木","金","土"];
 const lastRow = sheet.getLastRow();
 const month = Utilities.formatDate(targetDate, 'JST', 'MM');
 const day = Utilities.formatDate(targetDate, 'JST', 'dd');
 const date = month + "/" + day 
 const dateFinder = sheet.createTextFinder(date).findAll();
 
   for ( var i = 0; i < dateFinder.length; i++ ) {
       var dateColumn = dateFinder[i].getColumn();
   }
 
 const data = sheet.getRange(1,dateColumn,lastRow);
 let earlyTurnList = [];
 let dayShiftList = [];
 let lateTurnList =[];
 let nightShiftList = [];
 let afterNightShiftList = [];
 let holidayList = [];
 let paidVacationList = [];
 
 
 let earlyTurnFinder = data.createTextFinder('早').findAll();
 for ( let i = 0; i < earlyTurnFinder.length; i++ ) {
   let earlyTurn = earlyTurnFinder[i].offset( 0 , - dateColumn + 1).getValue();
   earlyTurnList.push(earlyTurn)
  }
 
 let dayShiftFinder = data.createTextFinder('日').findAll();
 for ( let i = 0; i < dayShiftFinder.length; i++ ) {
   let dayShift = dayShiftFinder[i].offset( 0 , - dateColumn + 1).getValue();
   dayShiftList.push(dayShift)
  }  
 
 let lateTurnFinder = data.createTextFinder('遅').findAll();
 for ( let i = 0; i < lateTurnFinder.length; i++ ) {
   let lateTurn = lateTurnFinder[i].offset( 0 , - dateColumn + 1).getValue();
   lateTurnList.push(lateTurn)
  } 
 
 let nightShiftFinder = data.createTextFinder('夜').findAll();
 for ( let i = 0; i < nightShiftFinder.length; i++ ) {
   let nightShift = nightShiftFinder[i].offset( 0 , - dateColumn + 1).getValue();
   nightShiftList.push(nightShift)
  }
 
 let afterNightShiftFinder = data.createTextFinder('明').findAll();
 for ( let i = 0; i < afterNightShiftFinder.length; i++ ) {
   let afterNightShift = afterNightShiftFinder[i].offset( 0 , - dateColumn + 1).getValue();
   afterNightShiftList.push(afterNightShift)
  }
 
 let holidayFinder = data.createTextFinder('公').findAll();
 for ( let i = 0; i < holidayFinder.length; i++ ) {
   let holiday = holidayFinder[i].offset( 0 , - dateColumn + 1).getValue();
   holidayList.push(holiday)
  }
 
 let paidVacationFinder = data.createTextFinder('有').findAll();
 for ( let i = 0; i < paidVacationFinder.length; i++ ) {
   let paidVacation = paidVacationFinder[i].offset( 0 , - dateColumn + 1).getValue();
   paidVacationList.push(paidVacation)
  }

 var text = date +"("+week[targetDate.getDay()]+") 勤務者一覧"+"\n\n" 
 if(earlyTurnList.length > 0){
   text = text +  "早番 : " + earlyTurnList +"\n\n"
 }  
 if(dayShiftList.length > 0){
   text = text +  "日勤 : " + dayShiftList +"\n\n"
 }  
 if(lateTurnList.length > 0){
   text = text +  "遅番 : " + lateTurnList +"\n\n"
 }
 if(nightShiftList.length > 0){
   text = text +  "夜勤 : " + nightShiftList +"\n\n"
 }
 if(afterNightShiftList.length > 0){
   text = text +  "明け : " + afterNightShiftList +"\n\n"
 }  
 if(holidayList.length + paidVacationList.length > 0){
  text = text +  "公・休・有 : \n" + holidayList+ "," + paidVacationList + "\n\n" ;
  }
 
 if(dayShiftList.length + earlyTurnList.length + lateTurnList.length + nightShiftList.length + afterNightShiftList.length < 1 ){
   var workers = 0; 
 }
 
 return  {text: text, workers: workers}; 
}

この記事が気に入ったらサポートをしてみませんか?