AdSheepは、新米バックエンドエンジニアの技術分野に留まらないライフハック風ブログです

GASを使ってSpreadSheetとSlackを連携させる

GAS

どうも、ShouNです。
勤務先では、新人が雑務を持ちまわりで担当しています。
当初はExcelファイルで管理していましたが、毎日各自が開いて当日の担当者を確認するのは面倒です。
また、確認し忘れて雑務を忘れてしまうこともありました。
今回は、この雑務当番表をスプレッドシート管理に変えて、毎朝自動でSlackに当日の担当をPostするようにした話になります。

GASとは

GoogleAppsScript入門より
Google Apps Scriptの略。
Googleの提供するサービスの機能を自分好みに拡張したり、他サービスと連携させたりできます。
また、関数単位でタイマーを指定できるので決まった時間に特定のコードを走らせたりもできます。
GoogleAppsScriptで毎日決まった時刻にスクリプトを実行するトリガー設定
実質JavaScriptで記述していきます。

目標

毎日定刻に、スプレッドシートで管理している当番表から当日の雑務担当を取得してSlackにPostするようにします。
調べていたら、先駆者様がいらっしゃいました。
似たようなことをしてますがこっちのほうがよっぽど複雑で難しいことしてます。
Slackの送信部だけ実装の参考にさせてもらいました。

おおよそのフロー

  1. GAS定刻起動
  2. 休日(祝日)の判定
  3. SpreadSheetから当日の担当者を取得
  4. SlackにPostするデータを作成
  5. SlackにPost
    Slack側はカスタムインテグレーションからincomingwebhook(受信Webhook)を設定します。
    incomingwebhookの設定については、SlackのIncomingWebhooksを使い倒すで詳しく解説されています。

SpreadSheetの構造

sheet1: 月単位の当番表

月曜から金曜までのカレンダーテーブルになってます。
日付セルの下には担当者の名前が振ってあります。

田中 松田 中田 田中 松田
中田 田中 松田 中田 田中

この構成上、sheet1はシステム実装月から毎月1枚ずつ増えていくことになります。
増やすのがいやなら既存のsheet1に来月の情報を上書きするとよいでしょう。
sheet1の名前を”西暦年/月”にしてプログラムが当番を取得する際のキーにしているので、そこの修正を忘れずにしましょう。

実装

function checkToban(){
// sheetId
const id = "*********************";
today = new Date();
dayOfWeek = today.getDay();
year = today.getFullYear();
month = today.getMonth() + 1;
var strYM = year + "/" + month;

// 土日でなければ処理続行
if(!checkHolidayToday()){
  sheet = SpreadsheetApp.openById(id);
  // カレンダーシート取得
  calenderSheet = sheet.getSheetByName(strYM);
  console.log(strYM);

  // 当番取得
  var staff = new Array();
  staff = getTodaysStaff();

  var slack_result = month + "月" + today.getDate() + "日の当番\n";
  for(var j = 0; j < staff.length; j++){
    if(j == staff.length-1){
      slack_result += staff[j] + "\n";
    }else{
      slack_result += staff[j] + "・";
    }
  }
  // post to slack
  sendSlack(slack_result);
}else{
// 休日なら処理終了
return;
}
}

/***************************************
 * 休日チェック
 ***************************************/
function checkHolidayToday(){
  // 休日判定
  if(dayOfWeek <= 0 || 6 <= dayOfWeek){
    return true;
  }
  // 祝日判定
  var calendarId = "ja.japanese#holiday@group.v.calendar.google.com";
  var calendar = CalendarApp.getCalendarById(calendarId);
  var todayEvents = calendar.getEventsForDay(today);
  if(todayEvents.length > 0){
    return true;
  }
  // 平日
  return false;
}

/***************************************
* 当番のチェック
***************************************/
function getTodaysStaff(){
// カレンダーシート内の電話当番表周辺マージン
var xMargin = 2; // 縦マージン(曜日表示含)
var yMargin = 1; // 横マージン

// 今日の担当者取得
var weekOfMonth = Math.floor(( today.getDate() - dayOfWeek + 12 ) / 7 );
var row = xMargin + weekOfMonth * 2;
var column = yMargin + dayOfWeek;
var range = calenderSheet.getRange(row, column);
var dutyNumbers = range.getValue();

// セルに,区切りで複数名の名前があった場合
if(dutyNumbers.indexOf(",") != 1){
var aryDutyStr = dutyNumbers.split(",");
}else{
var aryDutyStr[0] = dutyNumbers;
}
return aryDutyStr;
}
/***************************************
* Slackに投稿
***************************************/
function sendSlack(message)
{
// slack関連
var postUrl = "https://hooks.slack.com/services/****/*******/***"; // incomng webhookのPost ID
var postChannel = "#general"; // Channel
var username = 'staff'; // ユーザー名(表示名)
var icon = ':thinking_face:'; // アイコン

var jsonData =
{
"channel" : postChannel,
"username" : username,
"icon_emoji" : icon,
"text" : message
};
var payload = JSON.stringify(jsonData);
var options =
{
"method" : "post",
"contentType" : "application/json",
"payload" : payload
};

UrlFetchApp.fetch(postUrl, options);
}

各処理の解説

checkToban()

GASの定刻トリガーにはこの関数を指定します。
このシステムの根幹となる関数です。
idで連携したいSpreadSheetのhttps://docs.google.com/spreadsheets/d/xxxxxxxxxxxxxxxxxxx/~xxxxxxxxxxxxxxxxを指定します。
処理冒頭で起動時のDate情報を準備していますが、この情報は切り分けた担当者取得メソッドでも使います。
引数の数が多くなってしまうので今回は変数宣言varをつけずに意図的にグローバル変数にして、他の関数からも参照できるようにしました。
関数内で準備しているのにローカル変数にならないのは実装が肥大化すると混乱の種になるので非推奨らしいですが、JSスキル赤ちゃん程度の人間には引数で渡す以外の解決策がこれくらいしか思いつかなかった…。
もっといい方法絶対ある…。
JavaScriptのスコープを理解する
SpreadSheetの取得にはSpreadsheetApp.openById()に冒頭で指定したidを渡します。
この処理でSpreadSheet全体、つまり複数のsheetが存在した場合は全部のSheetを内包したSpreadSheetを取得します。
特定のSheetは、getSheetByName()にsheet名を渡して取得します。
その後、当番担当取得メソッドを呼び出して各担当者を取得しています。
最後に、取得した担当者名をSlackにPostするText用変数に組み込みます。

checkHolidayToday()

休日チェックを行うメソッドです。
Googleカレンダーと連携したことで、祝日にも対応しています。
メソッドの名前的に、休日なら真、平日なら偽を返します。

getStaff()

xMargin,yMarginはカレンダー表の周辺に空白列、行を挟んでいる場合に指定します。
例えばA列、1行目をそれぞれ空白にし、カレンダー左上端はB列、2行目に位置する場合、
xMarginは1,yMarginは2となります。(yMarginは処理の簡略化のため、曜日行もマージンに含めるようにしたため2になります)
getRange(row, column)を使うことで特定のセルを指定できますが、それだけではそのセル中の値自体は取得できていません。
セルの指定の後、getValue()を呼ぶことで初めてセル内の値を取得できます。
このメソッドでは、カレンダーシートの該当日セルから担当者名を取得して返しています。

sendSlack()

こちらはslackにPostする関数です。
ローカル変数にSlackにPostする情報を用意しています。
postUrlにはSlack側でincomingwebhookを設定した際に表示されたURLを置きます。
incomingwebhookでPostする時の名前やアイコンはSlack側でも設定できますが、こちらで指定してJSONに組み込んで指定もできます。
ここではstaffという名前で、アイコンには考える人の絵文字を設定しています。
UrlFetchApp.fetch()でSlackに送信します。

おまけ

JSで日付を取得

var today = new Date();

JSで月を取得

var today.getMonth();
0から11が戻り値です。それぞれ戻り値に+1して使います。

曜日を取得

today.getDay();
0から6が戻り値です。それぞれ日から土。

月の第何週か計算

// (日付-曜日+12)/7
// 小数点以下を処理する
Math.floor((today.getDate() - today.getDay() + 12) / 7)

参考

日時、曜日を取得する方法
GoogleAppsScriptでのDateオブジェクトの各生成方法、日付と時刻を分ける

まとめ

ShouN
ShouN

導入までのコストはかかりますが、日々の細かい作業を減らすことができます。
1日1分でも1年で365分になりますから、こういった細々とした面倒をなくしていくことは結構大事なことですよね。

コメント