Slack+スプレッドシート+GASで学習管理Bot

# 背景

Slackで学習時間を入力してスプレッドシートにも反映されたら楽だなー

# 機能

* チャンネルに自動通知
* チャンネルに投稿した内容をスプレッドシートに自動入力
* チャンネルへの投稿をトリガーにメッセージを返す

# 用意するもの

* Slackワークスペース＋パブリックチャネル
* Slack API
 * Incomming Webhook
 * Outgoing Webhook
* Google スプレッドシート
* Google Apps Script(GAS) プロジェクト

# 諸々設定

[こちら](https://techblog.gmo-ap.jp/2019/09/06/slack%E3%81%A8%E3%82%B9%E3%83%97%E3%83%AC%E3%83%83%E3%83%89%E3%82%B7%E3%83%BC%E3%83%88%E3%82%92%E9%80%A3%E6%90%BA%E3%81%97%E3%81%A6%E7%AD%8B%E3%83%88%E3%83%AC%E7%AE%A1%E7%90%86%E3%82%92%E3%81%99/)の記事にとても詳しく書かれていますので割愛。
（何ならこちらを元にして実装した）

# 完成形

* 管理シート

![スクリーンショット 2020-09-05 9.34.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609990/aed9987f-415d-964d-1221-cf3a4ec967e0.png)

* 学習内容記載シート（下に内容を追加していくと表への列追加、Slackへのメッセージ追加に自動で対応するようにした）

![スクリーンショット 2020-09-05 9.34.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609990/70f570c0-b7c8-0caf-48f0-9295182e3520.png)

* Slack

![スクリーンショット 2020-09-05 9.34.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609990/1cee9788-1c50-bcca-79ec-8a784069f53e.png)

1. GASプロジェクトで設定したトリガー元に毎日決まった時間に自動通知される
2. 内容を入力する
3. 入力が完了した旨を通知（成功失敗に関わらず出ちゃうけど）




# コード

```javascript
// 日報管理シートのID
const SHEET_ID = ;// https://docs.google.com/spreadsheets/d/xxx/edit#gid=yyyのxxxの部分
// Slackメンションユーザー
const USER_MENTION_ID = '<@' + （※ユーザーID） + '>'
// 通知先Slack Webhook
const SLACK_WEBHOOK_URL = ;// Incomming WebhookのURL
// SUM関数雛形
const FUNCTION_SUM = "=SUM(C_row:Z_row)"; // C,Zは表のフォーマットに合わせる、Zは仮の右端としているのでそれを超えるほどになったら別途対応必要・・・
// 記録する列の開始列
const RECORD_COLUMN = 3; // // 表のフォーマットに合わせる
// 管理シートURL
const RECORD_SHEET_URL = ; // 

// 日付文字列を生成する
function getDateString(date){
  return Utilities.formatDate(date, 'Asia/Tokyo', 'yyyy/MM/dd');
}

// Slackへメッセージ送信
function postSlack(text){
    var jsonData =
      {
        "text" : text
      };
  var payload = JSON.stringify(jsonData);
  var options = {
    "method" : "post",
    "contentType" : "application/json",
    "payload" : payload
  };
  UrlFetchApp.fetch(SLACK_WEBHOOK_URL, options);
}
 
// Slackにメッセージをポストする（トリガー設定）
function doMessage(e) {
  // その日の日付を記載する
  var sheet = SpreadsheetApp.openById(SHEET_ID).getSheetByName('学習管理');
  recordDateAndGetRecordRow(sheet);
  // SlackへPost
  postSlack(createMessage());
}

// 学習内容を取得
function getStudyContents(){
  var sheet = SpreadsheetApp.openById(SHEET_ID).getSheetByName('学習内容');
  return sheet.getRange(1,1,sheet.getLastRow(),1).getValues();
}

// シートから学習中の内容を取得しメッセージ文字列に組み込み
function createMessage(){
  var message = USER_MENTION_ID + "\n【" + getDateString(new Date()) + "】今日の学習内容をスペース区切りで記載しよう〜！\n"; 
  
  var contents = getStudyContents();
  for(var i in contents){
    message += contents[i]
    if(contents.length -1 != i){
      message += "/";
    }
  }
  return message;
  
}
 
// Slackからの入力を受け取る
function doPost(e) {
  // Slackの通知なら処理をスキップ
  if (e.parameter.user_name === "slackbot") return; 
  // Slackで入力した内容をスペースで分割
  var datas = e.parameter.text.split(/\s+/);
  Logger.log(datas)
  record(datas);
  //Slackへbotからのコメントをポスト
  postSlack(USER_MENTION_ID + "\n入力完了！\n<"+ RECORD_SHEET_URL +"|管理シート>"); 
}

// 学習内容を学習管理シートの列に反映
function updateHeading(sheet){
  var contents = getStudyContents();
  Logger.log(contents);
  for(var i in contents){
    sheet.getRange(1,RECORD_COLUMN + Number(i)).setValue(contents[i]);
  }
}

// 日付文字列を格納し、その行数を返却する
function recordDateAndGetRecordRow(sheet){
  var lastRow = sheet.getLastRow();
  var recordRow = lastRow + 1;
  
  var formatDate = getDateString(new Date());
  var lastRecordDate = sheet.getRange("A" + lastRow).getValue();
  var formatlastRecordDate = getDateString(lastRecordDate);
  
  if (formatDate == formatlastRecordDate) {
    recordRow = lastRow;
  }
  
  // 最終行の次の行に入力する
  sheet.getRange(recordRow,1).setValue(formatDate);
  // SUM関数も埋め込む
  sheet.getRange(recordRow,2).setFormula(FUNCTION_SUM.replace(/_row/g,recordRow));
  
  return recordRow;
}

// 入力した内容をシートに反映
function record (datas) {
  var sheet = SpreadsheetApp.openById(SHEET_ID).getSheetByName('学習管理');
  // ヘッダーの更新
  updateHeading(sheet);
  var recordRow = recordDateAndGetRecordRow(sheet);
  for(var i in datas){
    sheet.getRange(recordRow,RECORD_COLUMN + Number(i)).setValue(datas[i]);
  }
}

```

# 実装TIPS

* `<@ SlackユーザーID >`の書式を組み込むと対象ユーザーへのメンションに変換される 例：`<@ABCDEFGHI>`
* GASアプリケーションへのPOSTデータは`doPost(e)`という関数を用意すればキャッチできる、GETなら`doGet(e)`
* `setFomula(文字列)`で対象のセルに関数文字列としてセットすることができる
* for...in構文で取得されるインデックス番号にNumber()関数を適用しないと数値として扱えない（仕様なのかGAS上だけなのかは不明）

参考

* https://techblog.gmo-ap.jp/2019/09/06/slack%E3%81%A8%E3%82%B9%E3%83%97%E3%83%AC%E3%83%83%E3%83%89%E3%82%B7%E3%83%BC%E3%83%88%E3%82%92%E9%80%A3%E6%90%BA%E3%81%97%E3%81%A6%E7%AD%8B%E3%83%88%E3%83%AC%E7%AE%A1%E7%90%86%E3%82%92%E3%81%99/ （設定の項でも貼った記事）

# 後書き

その他が先頭なのはUIイケてないよなあ