GASで掃除当番Botを作ってみる ③ランダムで掃除当番決め(複数グループ)

前回までで、一つのグループ内でランダムで当番決め + 通知 ができました。
今回は、これを複数グループ対応にしていきます。

事前準備 #

スプレッドシート側 #

こんな感じで5グループにしてみました。
人数も増やしています。
グループ表

実装 #

※2019/12/29 再代入しない変数の宣言をconstに修正しました。
※2020/2/16
v8ランタイムに対応したため、letやアロー関数などが新たに使用できるようになりました。
これに伴いリファクタリングを行っています。

ランタイムの変更は、GASエディタを開いたときに表示される案内(Enable new Apps Script runtime powered by Chrome V8 for this project.)から変更するか、実行→Chrome V8を搭載した新しいApps Scriptランタイムを有効にする からできます。

//掃除当番をランダムで決めて通知
function noticeCleaningDuty() {
  let msg;
  try { // ⑤
    //連携するスプレッドシートのうち、グループ表のシート情報を取得
    const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('グループ表'); //①
    //指定シートの全ての値を取得(二次元配列)
    const data = sheet.getDataRange().getValues();
    //掃除当番のグループ
    const groupNames = ['A', 'B', 'C', 'D', 'E']; //②
    //掃除当番を決定し、通知メッセージを生成
    msg = decideCleaningDuty(data, groupNames); //③
  } catch (e) {
    msg = 'エラーが発生しました:' + '\nstack:\n' + e.stack;
  }

  try { // ⑤
    //Slack側 Incoming WebHookのURL
    const WEBHOOK_URL = PropertiesService.getScriptProperties().getProperty('WEBHOOK_URL');

    //Incoming WebHookに渡すパラメータ
    const jsonData =
        {
          'text': msg
        };

    //パラメータをJSONに変換
    const payload = JSON.stringify(jsonData);

    //送信オプション
    const options =
        {
          'method': 'post',
          'contentType': 'application/json',
          'payload': payload
        };
    //指定URL、オプションでリクエスト
    UrlFetchApp.fetch(WEBHOOK_URL, options);
  } catch (e) {
    Logger.log('送信エラー:' + '\nstack:\n' + e.stack);
  }
}

//掃除当番を決定し、通知メッセージを作成して返す ③
function decideCleaningDuty(data, groupNames) {
  let noticeMsg = '今週の掃除当番のお知らせ\n\n';
  for (let i = 0; i < groupNames.length; i++) {
    const groupNameRow = searchGroupNameRow(data, groupNames[i]);
    const lastColumn = getLastColumn(data, groupNameRow);
    const menbers = data[groupNameRow - 1].slice(2, lastColumn);
    const numbers = toDraw(menbers.length);
    noticeMsg += groupNames[i] + '' + menbers[numbers[0]] + '' + menbers[numbers[1]] + '\n';
  }
  noticeMsg+= '\nよろしくお願いします。'
  return noticeMsg;
}

//指定グループ名がある行番号を返す ④
function searchGroupNameRow(data, groupName) {
  for (let i = 0; i < data.length; i++) {
    if (data[i][0] === groupName) {
      return i + 1;
    }
  }
  throw new Error(groupName + 'グループのデータが見つかりませんでした。');
}

//指定した行の最終使用列番号を返す
function getLastColumn(data, row) {
  for (let i = 2; i <= data[row-1].length; i++) {
    if (data[row-1][i] === undefined || data[row-1][i] === '') {
      return i;
    }
  }
}

//掃除当番の抽選結果を返す
function toDraw(length) {
  let numbers = [];
  numbers[0] = random(length);
  do {
    numbers[1] = random(length);
  } while(numbers[0] === numbers[1]);
  return numbers;
}

//0~(length-1)の乱数を返す
function random(length) {
  //例 5人の場合 0~0.9999… * 5 の小数点切り捨てで、0~4になる
  return Math.floor(Math.random() * length);
}

大まかな変更箇所 #

① データを取得したいシートの指定でシート名で指定するように変更 #

const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0];からconst sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('グループ表');に変更し、取得したいシートの名称を指定するように変更。

② グループ名称の配列の用意を追加 #

今回は複数グループ対応にするため、グループ名称の配列を用意。

③ 通知メッセージの作成部分をメソッドに変更 #

掃除当番を決定し、通知メッセージを作成する処理をメソッド化。

④ 指定した名称に応じた行番号を取得するメソッドを追加 #

グループ名に応じた行の番号を取得するメソッドを追加。

上記の変更箇所はありますが、基本的には、前回まで書いていた処理を複数グループ分、forで回しているというだけです。

⑤ try~catchブロックを追加(2019/12/29追記) #

コードやスプレッドシートのデータがおかしいなどして、エラーになった場合、エラーメッセージをSlackに投稿してくれるよう追記しました。エラーメッセージがあることで、修正もしやすいです。

実行結果 #

Slack投稿結果

こんな感じになりましたー。
複数グループでの当番決めも、なんとかできました。
(前回と違い、メンバー名のところが敬称略形式になっていますが、そこは突っ込まないでください…)

ちなみにbotの名称とアイコンが変わっていますが、これはIncoming Webhook(着信Webフック)の設定から変更可能です。
webhook設定画面

※ちなみにJSONペイロードに渡すパラメータとして

var jsonData =
      {
        'text': msg,
        'username': 'new-bot-name',
        'icon_url': 'https://slack.com/img/icons/app-57.png'
        //もしくは 'icon_emoji': ':ghost:'
      };

など指定して、設定を上書きもできるそうです。
こちらは今回試してはいないのですが、容易に変更できるのを考えると、こちらで設定する方がいいかも…?(人によっての好みでしょうか…)


あとやれたらいいかなと思う機能として

  • 前回当番になった人を除外するようにする
  • これまで当番になった回数が少ない人が優先的に選ばれるようにする
  • 当番を決めた後、それをスプレッドシートに書き込んで履歴にする
  • 設定した時間で通知するようにする(これはトリガー設定だけなので恐らく簡単かなと)
    などでしょうか。

GASの勉強として続きをやるのもいいのですが、ちょっと他の勉強もしたいので、一旦ここまでにしておきます。
勉強したいとか、ちょっとやってみたいとか、これも知っておいた方がいいだろうなとか、そういったことが山積みでとても追いつかないですが(涙)

あまりきれいなコードではありませんが、
もし「GASでbot作るー」という方がいたとして、少しでも何かの参考にでもなれば幸いです。