スプレッドシートとSlackを連携させてタスク管理botを作る

## やりたいこと

**タスクと作業日の入力されたスプレッドシート**から、
**その日作業する必要のあるタスクを整形**して
**Slack に毎朝 POST してくれる**ような
タスク管理 bot を作成したい。

こんな感じの。
<img width="445" alt="スクリーンショット 2019-09-22 14.47.04.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/390806/0a952071-bba5-f2ed-999e-909259db4005.png">
というわけで、bot 作成までの流れを一通りまとめておきます。

**2020/ 4/ 5**
**GAS の V8 ランタイム導入によりテンプレートリテラルが使用可能になったため、サンプルコードを一部変更いたしました。**

## 準備

- Google スプレッドシート(タスクリスト)
- POST したい Slack チャンネル
- Slack Incoming Webhooks URL(後述します)

スプレッドシートはこんな感じのサンプルを用意します。
キャプチャには写っていませんが、シート名は `schedule` にしておきます。
<img width="807" alt="スクリーンショット 2019-09-22 14.46.10.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/390806/d5de9814-e546-18ff-177d-9ab49c2dd0a2.png">


## 実装する必要のある機能

- スプレッドシートからデータを取得
- Slack 用にテキスト整形
- Slack に POST
- 以上のスクリプトを毎朝実行

前準備として、スクリプトから Slack に POST する機能を実装するにあたって、
**Slack Incoming Webhooks** という外部連携の設定を行う必要があります。

## Slack Incoming Webhooks

POST したい Slack チャンネルの URL を取得、設定をしていきます。

こちらの記事を参考にしつつ。
[Slack の Webhook URL 取得手順](https://qiita.com/vmmhypervisor/items/18c99624a84df8b31008)
設定ができたら、ターミナルから簡単なメッセージを投げてみて動作確認をしてみましょう。

`curl -X POST -H 'Content-type: application/json' --data '{"text":"SampleText"}' https://hooks.slack.com/services/取得したURL`

上記のコマンドを実行して、Slack に SampleText が POST されれば OK です。

## GAS から簡単なメッセージを投げてみる

機能実装のためのスクリプトは **GoogleAppScript(GAS)** に記述していきます。
JavaScript ベースなので、フロントエンドの方は作業しやすいのではないかと思います。
(~~ES6 には対応しておらず、let 宣言やテンプレートリテラルなどは使用できないようです。つらい。~~)
V8 ランタイム導入により ES6 記法に対応されたようですね、うれしい！

GAS では、
1. 先にスクリプトを作成してスプレッドシートに紐つける方法と、
2. スプレッドシートに紐ついたスクリプトを生成する
方法があるようです。

今回は後者の方法で進めていきますので、スプレッドシートのメニューから
`ツール > スクリプトエディタ`
で作成します。

メッセージ形成の機能を後で追加しやすいように、WebhookURL を管理する部分とメッセージの作成部分を別関数で作成していきます。

```js
function sendMessage() {
  //Webhook URLを以下に入力
  const postUrl = "https://hooks.slack.com/services/取得したWebhookをここに入力します";
  const sendMessage = createMessage();
  const jsonData = {
    "text": sendMessage
  };
  const payload = JSON.stringify(jsonData);
  const options = {
    "method": "post",
    "contentType": "application/json",
    "payload": payload
  };
  UrlFetchApp.fetch(postUrl, options);
}

function createMessage() {
  //ここでメッセージの中身を作成していきます
  var message = "SampleText";
  return message;
}
```

スクリプトが準備できたら、メニューから sendMessage を実行します。
先ほどと同様、Slack に SampleText が POST されれば、GAS と Slack 連携の機能実装は完了です。

## スプレッドシートのセルを取得して Slack に POST してみる

sendMessage は変更する必要がないので、createMessage の中身のみ書き換えていきます。

```js
function createMessage() {
  //spreadsheetからscheduleのシートを取得
  const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = spreadsheet.getSheetByName("schedule");//シート名

  const output = sheet.getRange(2, 3).getValue(); //C2(2行目, 3列目セル)の値を取得
  return output;
}
```

## スプレッドシートから出力するタスクを整形

スプレッドシートから必要な情報をいい感じに POST できるように、タスクを整形していきます。
以下のような感じで機能を実装していきます。

- 作業予定日に本日の日付が入力されていれば、作業内容と納品日を整形してメッセージに追加
- 備考もあれば追加
- これをデータのある全ての行に対して実行
- タスクがなければ`本日のタスクはありません`と POST

```js
function createMessage() {
  //spreadsheetからscheduleのシートを取得
  const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = spreadsheet.getSheetByName("schedule");

  const dataObject = new Date();
  const today = Utilities.formatDate(dataObject, "Asia/Tokyo", "yyyy/MM/dd");

  const detailColumn = 2; //作業内容の入力されているカラム
  const workDateColumn = 3; //作業日が入力されているカラム (C列)
  const publishDateColumn = 4; //公開日の入力されているカラム
  const commentColumn = 5; //備考が入力されているカラム
  const startRow = 2; //列データはタイトルを除き2行目から

  const columnFVals = sheet.getRange('C:C').getValues(); //C列の値を配列で取得
  const lastRow = columnFVals.filter(String).length; //空白を除き、配列の数を取得

  var taskList = ""; //タスクリスト
  var output = ""; //createMessageからの最終的な出力

  for (row = startRow; row <= lastRow; row++) {
    var detail = sheet.getRange(row, detailColumn).getValue() + " ";
    //作業予定日を取得
    var workDate = sheet.getRange(row, workDateColumn).getValue();
    var publishDate = sheet.getRange(row, publishDateColumn).getValue();
    var comment = sheet.getRange(row, commentColumn).getValue();

    //日付の整形
    workDate = Utilities.formatDate(workDate, "Asia/Tokyo", "yyyy/MM/dd");
    publishDate = Utilities.formatDate(publishDate, "Asia/Tokyo", "MM/dd");

    //今日の日付と作業予定日を比較、taskListの生成
    if (today == workDate) {
      taskList = `${taskList}◆ ${detail}納品日時：${publishDate} ${publishTime}` + "\n";
      if (comment !== "") {
        taskList = `${taskList}  - ${comment}` + "\n";
      }
    }
  }

  //最終的な出力の生成
  if (taskList == "") {
    output = `${today}のタスクはありません`;
  } else {
    output = `${today}のタスクです \`\`\`${taskList}\`\`\``;
  }

  return output;
}
```

こうして出来上がったものが、一番最初に貼り付けてあったこちらになります。
<img width="445" alt="スクリーンショット 2019-09-22 14.47.04.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/390806/9f4832d6-aba2-6e7e-4b5d-43833154e315.png">
タスクリストを出力するためには sendMessage 関数を実行する必要があるので、
最後に **GAS が毎朝 sendMessage 関数を実行する**ように設定します。

## 関数の定期的な実行

GAS に実行タイマーを設定すれば OK です。
下記赤枠部分から実行トリガーを設定することができます。
<img width="526" alt="スクリーンショット 2019-09-22 15.11.17.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/390806/30ddd630-2c41-e4ea-43a7-2fde4b0832a2.png">

`トリガーを追加` → 以下のように設定で完了です。
![スクリーンショット 2019-09-22 15.16.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/390806/1a7975ab-168e-7b76-d68b-eba8f159e690.png)

これで毎日 9-10 時くらいにその日のタスクを通知してくれる bot ができました。
うれしい。