< Back

月末営業日にタスクをリマインドする ~ 使うのはSlackとGoogleアカウントだけで10分くらいで実装できるよ ~

月末って忙しいよね…

月末って報告書とか、領収証とか…なんかたくさん出さなきゃいけないものあるよね。センパイが「誰か作って」って言ってたのをGoogle apps scriptで作成しました。
Google apps script[GAS]触りだして4日目くらいですが、かなり便利なのでエンジニア以外のもっとたくさんの人に触れて欲しいなと思い、連日ですがGASの実用例をあげたいと思います。

完成品

月末の営業日にSlack投げるだけのシンプルな実装です。
コピペすれば10分で実装いけるんじゃないかと!

スクリーンショット 2017-03-06 21.29.19.jpg

完成までに使用するもの

Googleアカウント[Google Apps Script]
Slack

完成したら

以下の2つもコード公開します
* 月初めの営業日になにかをする
* 月始めの第一金曜日になにかをする

つまりこれを応用すれば、いつ何時でもSlackで自動チャットを飛ばせるようになる。

月末締め…!!

画像ありでどんどんいくよ〜〜

Slack Tokenの取得

ここを参考にしてみて!
http://qiita.com/ykhirao/items/0d6b9f4a0cc626884dbb

xoxp-18127 こんな感じの長い数字。

Slack持ってない人は個人Slackのススメにしたがって導入をすすめます。

GASの導入

Googleドライブにアクセス
https://drive.google.com/drive/my-drive

スクリーンショット 2017-03-06 21.59.16.jpg

新規 >> その他 >> アプリを追加
から Google apps script を検索・追加

スクリーンショット 2017-03-06 22.00.23.jpg

そうすると新規 >> その他 >> Google apps scriptを押してみてください。
GAS画面になると思います。

GASの編集

スクリーンショット 2017-03-06 22.23.56.jpg

メインの部分にこれを貼り付けてみよう!


/*
  メインの処理
  setTriggerDay() : 最終営業日(0時)に次の処理をする命令を出す。
  setTriggerHoursLast() : 最終営業日の18時に次の命令を出す。
  sendSlack() : slackにデータを投げる。

  サブ的な処理
  deleteTrigger() : 蓄積されている命令を削除する
  lastBusinessDay() : 今月の最終営業日を求める
  isHoliday() : 本日が[日本の祝日]かどうかチェック。土日は曜日で判定してるチェック
  .getDay() : Dateオブジェクトから曜日を求めるメソッド(0:日, 6:土曜日)
*/

function setTriggerDay()
{  
  var last = lastBusinessDay();
  ScriptApp.newTrigger("setTriggerHoursLast")
    .timeBased()
    .atDate(last.getFullYear(), last.getMonth()+1, last.getDate())
    .create();
}

function setTriggerHoursLast()
{
  deleteTrigger("setTriggerHoursLast");
  ScriptApp.newTrigger("sendSlack")
    .timeBased()
    .after( 18 * 60 * 60 * 1000 )
    .create();
}

function sendSlack() 
{
  deleteTrigger("sendSlack");
  var options = 
  {
    "method" : "POST",
    "payload" : 
    {
      "token": "********************",
      "channel": "bot-test",
      "text": "領収書出しましたか?忘れてないよね。\nhttp://.../"
    }
  }
  var url = "https://slack.com/api/chat.postMessage"
  UrlFetchApp.fetch(url, options);
}

function lastBusinessDay() 
{
  var today = new Date();

  var lastDayOfThisMonth = new Date(today.getFullYear(), today.getMonth()+1, 0);
  var day; // 0->日曜日

  for (var i = 0; i < 30; i++) {
    day = lastDayOfThisMonth.getDay();
    if (day == 0 || day == 6 || isHoliday(lastDayOfThisMonth)) {
      lastDayOfThisMonth = new Date(today.getFullYear(), today.getMonth()+1, -1 * i);
      continue;
    }
  }
  return lastDayOfThisMonth;
}

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

function isHoliday(day) 
{
  var startDate = new Date(day.setHours(0, 0, 0, 0));
  var endDate = new Date(day.setHours(23, 59, 59));
  var cal = CalendarApp.getCalendarById("ja.japanese#holiday@group.v.calendar.google.com");
  var holidays =  cal.getEvents(startDate, endDate);
  return holidays.length != 0; // 祝日ならtrue
}

sendSlackのここは個人設定に変えてください!

  • "token": "********************",
  • "channel": "bot-test",
  • "text": "領収書出しましたか?忘れてないよね。\nhttp://.../"

setTriggerDay()の実行と取り消しと毎月実行

実行 >> setTriggerDay
をおしてみよう。

スクリーンショット 2017-03-06 22.26.52.jpg

リソース >> 現在のプロジェクトのトリガー
ここからトリガー(何時にあれを実行しなさい)を確認できます。

スクリーンショット 2017-03-06 22.27.40.jpg

ここで 2017/03/21 夜中丁度に setTriggerHoursLast が実行されるようになっていたら大丈夫。setTriggerHoursLastは18時間後にSlack送りなさいという命令です。

スクリーンショット 2017-03-06 22.27.47.jpg

またついでにこの画面でsetTriggerDayを毎月1日6時くらいにセットします。

スクリーンショット 2017-03-06 22.37.18.jpg

そうすると毎月1日にsetTriggerDayが実行されて
-> setTriggerDay :最終日に次を実行
-> setTriggerHoursLast :18時に次を実行
-> sendSlack :Slackを実行

って感じで毎月、最終日トリガーを作成してくれます。

functionごとに見ていく

簡単にみていきます。

setTriggerDay

function setTriggerDay()
{  
  var last = lastBusinessDay();
  ScriptApp.newTrigger("setTriggerHoursLast")
    .timeBased()
    .atDate(last.getFullYear(), last.getMonth()+1, last.getDate())
    .create();
}

lastBusinessDay()で営業日最終日のDateオブジェクトを取ってきて
.atDate(last.getMonth()+1)としているところは、月は0月始まりとなっているからです。

setTriggerHoursLast

function setTriggerHoursLast()
{
  deleteTrigger("setTriggerHoursLast");
  ScriptApp.newTrigger("sendSlack")
    .timeBased()
    .after( 18 * 60 * 60 * 1000 )
    .create();
}

.after( 18 * 60 * 60 * 1000 )でミリ秒後にトリガーをセットしてます。

sendSlack

function sendSlack() 
{
  deleteTrigger("sendSlack");
  var options = 
  {
    "method" : "POST",
    "payload" : 
    {
      "token": "********************",
      "channel": "bot-test",
      "text": "領収書出しましたか?忘れてないよね。\nhttp://.../"
    }
  }
  var url = "https://slack.com/api/chat.postMessage"
  UrlFetchApp.fetch(url, options);
}

こういう書き方だと思っていただければ…
詳しくは公式ドキュメントで https://api.slack.com/methods/chat.postMessage

lastBusinessDay

function lastBusinessDay() 
{
  var today = new Date();
  var lastDayOfThisMonth = new Date(today.getFullYear(), today.getMonth()+1, 0);
  var day; // 0->日曜日

  for (var i = 0; i < 30; i++) {
    day = lastDayOfThisMonth.getDay();
    if (day == 0 || day == 6 || isHoliday(lastDayOfThisMonth)) {
      lastDayOfThisMonth = new Date(today.getFullYear(), today.getMonth()+1, -1 * i);
      continue;
    }
    break;
  }
  return lastDayOfThisMonth;
}

day == 0 || day == 6 || isHoliday(lastDayOfThisMonth)
ここで土日もしくは祝日判定をしています。

deleteTrigger


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

実は自分でセットしたtriggerってこんな感じで1分ごとにたまっていきます。
今回だと"MAIAMAIN"って名前のトリガーを全削除する機能になってます。
スクリーンショット 2017-03-06 23.14.29.jpg

参考: http://qiita.com/sumi-engraphia/items/465dd027e17f44da4d6a

isHoliday


function isHoliday(day) 
{
  var startDate = new Date(day.setHours(0, 0, 0, 0));
  var endDate = new Date(day.setHours(23, 59, 59));
  var cal = CalendarApp.getCalendarById("ja.japanese#holiday@group.v.calendar.google.com");
  var holidays =  cal.getEvents(startDate, endDate);
  return holidays.length != 0; // 祝日ならtrue
}

getCalendarByIdでGoogleの[日本の祝日]を取得してきてます。

参考: http://qiita.com/kamatama_41/items/be40e05524530920a9d9

終わりました〜〜
次は月初めの営業日を取っていきます〜〜

月初めの営業日


/*
  メインの処理
  setTriggerDay() : はじめの営業日(0時)に次の処理をする命令を出す。
  setTriggerHoursFirst() : はじめの営業日の11時に次の命令を出す。
  sendSlack() : slackにデータを投げる。

  サブ的な処理
  deleteTrigger() : 蓄積されている命令を削除する
  firstNextBusinessDay() : 来月のはじめの営業日を求める
  isHoliday() : 本日が[日本の祝日]かどうかチェック。土日は曜日で判定してるチェック
  .getDay() : Dateオブジェクトから曜日を求めるメソッド(0:日, 6:土曜日)
*/

function setTriggerDay2()
{  
  var first = firstNextBusinessDay();
  ScriptApp.newTrigger("setTriggerHoursFirst")
    .timeBased()
    .atDate(first.getFullYear(), first.getMonth()+1, first.getDate())
    .create();
}

function setTriggerHoursFirst()
{
 ScriptApp.newTrigger("sendSlack")
   .timeBased()
   .after(11 * 60 * 60 * 1000)
   .create();
}

function sendSlack() 
{
  var options = 
  {
    "method" : "POST",
    "payload" : 
    {
      "token": "********************",
      "channel": "bot-test",
      "text": "月が始まりました。"
    }
  }
  var url = "https://slack.com/api/chat.postMessage"
  UrlFetchApp.fetch(url, options);
}

function firstNextBusinessDay() 
{
  var today = new Date();  
  var firstDayOfNextMonth = new Date(today.getFullYear(), today.getMonth()+1, 1);
  var day; // 0->日曜日

  for (var i = 2; i < 32; i++) {
    day = firstDayOfNextMonth.getDay();
    if (day == 0 || day == 6 || isHoliday(firstDayOfNextMonth)) {
      firstDayOfThisMonth = new Date(today.getFullYear(), today.getMonth(), i);
      continue;
    }
  }
  return firstDayOfNextMonth;
}

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

function isHoliday(day) 
{
  var startDate = new Date(day.setHours(0, 0, 0, 0));
  var endDate = new Date(day.setHours(23, 59, 59));
  var cal = CalendarApp.getCalendarById("ja.japanese#holiday@group.v.calendar.google.com");
  var holidays =  cal.getEvents(startDate, endDate);
  return holidays.length != 0; // 祝日ならtrue
}

第一月曜日営業日


function firstNextBusinessMonDay() {
  var today = new Date();

  var firstDayOfNextMonth = new Date(today.getFullYear(), today.getMonth()+1, 1);
  var day; // 0->日曜日

  for (var i = 2; i < 32; i++) {
    day = firstDayOfNextMonth.getDay();
    if (day != 1 || isHoliday(firstDayOfNextMonth)) {
      firstDayOfThisMonth = new Date(today.getFullYear(), today.getMonth(), i);
      continue;
    }
    break;
  }
  return firstDayOfNextMonth;
}

ここだけ変えればいいかな、こんな感じ?
1日から探索して、月曜日以外もしくは祝日なら次の日にいく。

以上です。

終わりに

重要なことはそのままおコピペしてなんかやらかしちゃったとか責任とれないんで(ミスあっても、Slackがあれるとか、もしくは通知されないとか?)自身の環境でデバックしてからお使いください!

Google Apps Script楽しい!

.