SpreadsheetとLINE Notifyで日程調整

#背景
サークルの活動をなるべく多くの人が来られる日に行いたいので，毎週日程調整を行う．
LINEスケジュールや調整さん，ノートにスタンプ押してもらうなどのやり方だと，毎週手動で作るのが面倒．
そこで，スプレッドシートのチェックボックスで出欠を取り，そのリンクを自動で通知するようにした．

#作ったもの
- Google Spreadsheet＋GAS＋LINE Notifyを利用
- ユーザは日～土までのチェックボックスにチェックを付ける
- チェックが一番多い曜日をハイライトする
- LINEグループに毎週自動でリンクを通知する（このときチェックをすべて外す）

<img src="https://qiita-image-store.s3.amazonaws.com/0/239014/8c91ea59-9267-392b-18b8-2725fcda0b6d.png" width=30%> <img src="https://qiita-image-store.s3.amazonaws.com/0/239014/87870ed4-a794-d115-aeca-55b25a0dfde3.png" width=40%>
※木曜がないのはサークルの都合です

#実装
Spreadsheetの共有リンクを取得し（「リンクを知っている全員が編集可」を選択），それをLINE Notifyで通知します．GASからLINE Notifyを利用する方法は以下の記事を参考にしてください．

- [Google Apps ScriptからLINE NotifyでLINEにメッセージを送る]
(https://qiita.com/tadaken3/items/5f916a12587e42ece814)

以下のようにGASのトリガーを設定します．毎週決まった時間帯にnotifyが実行されてLINEグループに通知を送り，ユーザがチェックを付けたり消したりするとonEditが呼ばれてチェックが最も多い行をハイライトします．
![5.PNG](https://qiita-image-store.s3.amazonaws.com/0/239014/683689bc-c10e-0fec-2496-bd557cda894f.png)


```Javascript
var spreadsheet,
    sheet,
    lastRow,
    lastColumn,
    dataRange,   //全チェックボックスのセル範囲
    checkboxes,  //全チェックボックスの状態を格納する2次元配列
    daysNum = 6; //選択できる曜日の数

//LINEグループにスプレッドシートのリンクを通知する
function notify(){
  clearCheckBox();  //チェックボックスを初期化
  var message = "https://docs.google.com/spreadsheets/d/...",  //Spreadsheetのリンク
      url = "https://notify-api.line.me/api/notify",
      token = "hoge",  //LineNotifyから取得したトークン
      options = {
        "method" : "post",
        "payload" : "message= " + message,
        "headers" : {"Authorization" : "Bearer " + token}
  };
  UrlFetchApp.fetch(url,options);
}

//各オブジェクトを取得
function initialize(){
  spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  sheet = spreadsheet.getActiveSheet();  
  lastRow = sheet.getLastRow();
  lastColumn = sheet.getLastColumn();  
  dataRange = sheet.getRange(2, 2, lastRow - 1, lastColumn - 1);  //チェックボックスのセル範囲を取得
  checkboxes = dataRange.getValues();  //全チェックボックスの状態を2次元配列として取得
}

//シートが編集されたら，最もチェックの多い曜日（複数可）の列をハイライトする
function onEdit(e){  
  var checkCount = Array.apply(null, Array(daysNum)).map(function() {return 0});  //各曜日のチェック数（0で初期化）
  var maxCheckNum = 0;  //最多のチェック数
  var candidates = [];  //候補日（参加者が最多の曜日）
  
  initialize();
  
  //日曜から順に各曜日のチェックを数える
  for(var day = 0; day < daysNum; day++){
    for(var member = 0; member < lastRow - 1; member++) {
      if(checkboxes[member][day] === true)
        checkCount[day]++;
    }
    if(checkCount[day] < maxCheckNum) continue;
    
    if(checkCount[day] > maxCheckNum) candidates = [];  //最多チェック数を更新したらそれまでの候補日を削除
    candidates.push(day);  //候補日に追加
    maxCheckNum = checkCount[day];
  }
  
  //候補日をハイライトする
  dataRange.setBackground("#FFFFFF");  //ハイライトを初期化
  if(maxCheckNum === 0) return;  //チェックが1つもなければハイライトしない
  candidates.forEach(function(candidate){
    sheet.getRange(2, 2 + candidate, lastRow - 1, 1).setBackground("#B9D8A5");
  });
}

//チェックボックスのチェックをすべて外す
function clearCheckBox(){
  initialize();
  
  for(var day = 0; day < daysNum; day++){
    for(var member = 0; member < lastRow - 1; member++) {
      checkboxes[member][day] = false;
    }
  }
  dataRange.setBackground("#FFFFFF");  //ハイライトを初期化
  dataRange.setValues(checkboxes);
}
```

#ポイント
getRange()では行・列が0ではなく1から始まります．
また，getRange()で取得したデータをループで何度も参照すると遅いので，2次元配列に格納しておきます．

#まとめ
GASによる自動化に憧れて，初めて書いてみました．車輪の再発明感は多分にありますが，実際に利用した感じでは，自作なので既存のサービスよりかゆいところに手が届いて便利だと思います．他のメンバーも特に抵抗なく使えているようです．

GASもJSも初めてなので，もっと簡潔な書き方があれば教えていただきたいです．