Googleフォームで日報フォームを作成して回答したらSlackのチームチャンネルに共有されるようにした

## はじめに

あけましておめでとうございます。

日報書いてますか？
私の所属するチームには日報の文化はないのですが、日々の振り返りのために自主的に日報を書き始めました。

新人研修の頃にはテキストエディタを開いてマークダウン形式で日報を書いていました。
書き終わったらテキストをコピーしてSlackの新人研修チャンネルにスニペットで貼り付けていました。
これはイケてない。
今日のスケジュールを振り返るためにいちいちカレンダーを開かなくてはいけないし、夕礼の時間までに日報を書かなくてはいけないのに忘れてしまうこともしばしば。書き終わってからいちいちコピペするのも面倒です。

私の理想の日報体験は以下の通りです。

- 終業時間のちょっと前に日報のリマインダーが欲しい
- 今日のスケジュールを振り返りながら書きたい
- 書き終わったらSlackのチームチャンネルに日報を共有したい

これをGoogleフォームとGoogle Apps Scriptの組み合わせで実現できましたので紹介します。
以下、Google Apps ScriptをGASと表記します。

## Googleフォームで日報フォームを作成

まずはこんな感じでフォームを作ります。
![スクリーンショット 2020-01-01 17.09.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334984/d7b3b635-b5bd-3a2f-9d07-98718716a896.png)


## フォームに今日のスケジュールを追加しSlackに回答用URLを送信するGASを書く

GASで新規プロジェクトを作成してスクリプトを書きます。

```javascript
function main() {
  // GoogleフォームをID指定でオープン
  var form = FormApp.openById('abcd');
  
  // フォームのタイトルに今日の日付を設定
  var today = new Date();
  var year = today.getYear() + '';
  var month = (today.getMonth() + 1)+ '';
  var day = today.getDate() + '';
  var form_desc = year + '年' + month + '月' + day + '日';
  form.setTitle('日報@' + form_desc);
  
  var schedules = getTodayEvents();
  form.setDescription(schedules)
  
  // 回答用のURLをSlackに投稿
  form_url = form.getPublishedUrl();
  postMessage(form_url);
}

function getTodayEvents() {
  // アカウントのデフォルトカレンダーから
  // 00:00~23:59までのイベントを取得
  var now = new Date();
  var today_start = new Date(now.getYear(), now.getMonth(), now.getDate());
  var today_end = new Date(today_start.getTime() + (23 * 60 * 60 * 1000 + 59 * 60 * 1000));
  var events = CalendarApp.getDefaultCalendar().getEvents(today_start, today_end);
  
  var allDayEvents = []
  var notAllDayEvents = []
  var text = '';
  
  // allDayイベントとそうでないイベントをわける
  for (var i = 0; i < events.length; i++) {
    if (events[i].isAllDayEvent()) {
      allDayEvents.push(events[i]);
    } else {
      notAllDayEvents.push(events[i]);
    }
  }
  
  notAllDayEvents.sort(function(left, right) {
    // 開始時間でソートする
    var l_start = left.getStartTime().getHours() * 100 + left.getStartTime().getMinutes();
    var r_start = right.getStartTime().getHours() * 100 + right.getStartTime().getMinutes();
    if (l_start < r_start) return -1;
    if (l_start > r_start) return 1;
    
    // 開始時間が同じなら終了時間でソートする
    var l_end = left.getEndTime().getHours() * 100 + left.getEndTime().getMinutes();
    var r_end = right.getEndTime().getHours() * 100 + right.getEndTime().getMinutes();
    if (l_end < r_end) return -1;
    if (l_end > r_end) return 1;
    return 0;
  });
  
  // allDayイベント
  for (var i = 0; i < allDayEvents.length; i++) {       
    text = text + allDayEvents[i].getTitle() + '\n';
  }
  
  // 日中イベント
  for (var i = 0; i < notAllDayEvents.length; i++) {
    var startTime = notAllDayEvents[i].getStartTime();
    var endTime = notAllDayEvents[i].getEndTime();
    var start = ('00' + startTime.getHours()).slice(-2) + ':' + ('00' + startTime.getMinutes()).slice(-2);
    var end = ('00' + endTime.getHours()).slice(-2) + ':' + ('00' + endTime.getMinutes()).slice(-2);
    text = text + start + '~' + end + ': '+ notAllDayEvents[i].getTitle() + '\n';
  }
  
  return text
}

function postMessage(form_url) {
  
  // incoming webhookのURL
  var url = "https://hooks.slack.com/services/A/AAAAAAAA"

  var block = {
    "type": "section",
    "text": {
      "type": "mrkdwn",
      "text": "日報を書きましょう\n<" + form_url + "|リンク>"
    }
  }
  
  var payload = {
    "text": '日報の時間です',
    "blocks": [block]
  }
  
  // POSTオプション
  var options = {
    "method" : "post",
    "contentType": 'application/json',
    "payload": JSON.stringify(payload),
    "muteHttpExceptions": true
  }
  
  // POSTリクエスト
  var response = UrlFetchApp.fetch(url, options);  
  var responseCode = response.getResponseCode();
}
```

Slackに投稿するところは`incoming webhook`でも`chat.postMessage`でもお好きな方法でどうぞ。

GASを実行すると許可を求められるので内容を確認したうえで許可します。
![スクリーンショット 2020-01-01 10.58.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334984/55cfef28-7487-7343-fce5-8b61aa08a24b.png)

問題なければSlackに回答用フォームのリンクが投稿されます。
<img width="280" alt="スクリーンショット 2020-01-01 16.51.31.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334984/e3132ec1-5ba4-f8da-4a70-029f717a411f.png">

リンクを開くとフォームが表示されます。
フォームのタイトルには今日の日付が入り、説明欄には今日のスケジュールが表示されていい感じ :thumbsup:
<img width="695" alt="スクリーンショット 2020-01-01 17.11.44.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334984/f0cd5d39-bfeb-97ee-f485-d3a13ec8f98b.png">


## Slackにリマインダーを投稿

GASが平日の夕方に実行されるようにトリガーを設定します。
毎週月曜日、毎週火曜日、毎週水曜日、毎週木曜日、毎週金曜日にトリガーを追加します。
もうちょっと柔軟な設定ができるといいんだけどなぁ。

![スクリーンショット 2020-01-01 17.44.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334984/cd275aff-cccb-d5df-0d3c-51e593f158db.png)



## フォームに回答すると実行されるGASを書く

フォーム編集画面を開き、右上のボタンを押してスクリプトエディタを選択します。
![スクリーンショット 2020-01-01 11.43.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334984/1ddb9e11-0e20-1cf8-b079-50a664d6b021.png)

フォーム回答時に実行されるGASを書きます。
Googleフォーム回答時に受け取るイベントオブジェクトについての詳細は↓のGoogle Forms eventsのForm submitをご覧ください。
[Event Objects  |  Apps Script  |  Google Developers](https://developers.google.com/apps-script/guides/triggers/events)

```javascript
function main(e) {  
  // フォーム回答時にはEvent Objectを受け取る
  var form_response = e.response;
  
  // Slackに投稿するペイロードを作成
  var payload = build_payload(form_response)
  postMessage(payload);
}

function build_payload(form_response) {
  var payload = {
    'blocks': []
  }
  
  var itemResponses = form_response.getItemResponses();
  
  for (var i = 0; i < itemResponses.length; i++) {
    var itemResponse = itemResponses[i];
    
    var title = itemResponse.getItem().getTitle();
    var response = itemResponse.getResponse();
    
    var block = {
      'type': 'section',
      'text': {
        'type': 'mrkdwn',
        'text': '*' + title + '*' + '\n' + response
       }
    };
    payload.blocks.push(block);
  }
    
  return payload
}

function postMessage(payload) {
  
  // incoming webhookのURL
  var url = "https://hooks.slack.com/services/A/AAAAAAAA"

  // POSTオプション
  var options = {
    "method" : "post",
    "contentType": 'application/json',
    "payload": JSON.stringify(payload),
    "muteHttpExceptions": true
  }
  
  // POSTリクエスト
  var response = UrlFetchApp.fetch(url, options);  
  var responseCode = response.getResponseCode();
}
```

回答されるとスクリプトが実行されるようにトリガーを設定します。
赤枠のなかがポイントです。
![スクリーンショット 2020-01-01 16.21.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334984/822819c0-c9f5-11f7-8d10-87b48e223640.png)


## デモ

Slackに送られてきたURLを開いてフォームに回答します。
<img width="831" alt="スクリーンショット 2020-01-01 16.23.12.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334984/0993ffdd-b93e-478a-234c-5c9c7363d281.png">

送信ボタンを押すとSlackに回答が投稿されます。
<img width="484" alt="スクリーンショット 2020-01-01 16.24.00.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334984/2f67cb46-5feb-8bf9-09d8-b4ea59793fdf.png">

うまくいきました。



## 参考
### Google Developers
[Class FormApp  |  Apps Script  |  Google Developers](https://developers.google.com/apps-script/reference/forms/form-app)
[Event Objects  |  Apps Script  |  Google Developers](https://developers.google.com/apps-script/guides/triggers/events)
[Class UrlFetchApp  |  Apps Script  |  Google Developers](https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app)
### Slack
[Slack でのIncoming Webhook の利用 | Slack](https://slack.com/intl/ja-jp/help/articles/115005265063)
[chat.postMessage method | Slack](https://api.slack.com/methods/chat.postMessage)