Botもサーバーも不要！SlackとGASで簡単な共有＆アンケートツールを作ってみた


こんちには、エバーセンスの江上です。

Slackつかってますか？僕は `/remind` に幾度救われたかわかりません。最近1年前に設定したremindのおかげで、プロバイダーから3000円返ってきました。
そんなremindのメッセージにボタンがついてるの知ってますか？
（↓のMark as CompleteとかDeleteとか）
![message button.png](https://qiita-image-store.s3.amazonaws.com/0/68146/f3244a41-efed-5be5-31e5-edac669a27af.png "message button.png")

この超便利機能を、自分でも作れると聞いて、早速試してみました ^o^

ちなみに今回は、Botもサーバーも運用が大変なので、 `Google App Script` に全てお任せしています。
よって、foreverがハングしたりec2が急に落ちたりする世界とは無縁で運用できます！
Googleさんありがとー。

## 必要なもの
- googleアカウント
- slackアカウント

## 参考情報
- [slack appでできること](https://api.slack.com/internal-integrations)
- [GASでwebアプリケーションを作る方法](https://tonari-it.com/gas-web-application-beginner/)


## やってみる

とりあえず、『毎日「今日の小技」をSlackに共有して、それが役に立ったかどうかを回答してもらう』システムをつくることにしました。

やることはこんな感じ
1. Slackに「今日の小技」を通知する
2. ユーザーがボタンを押したらフィードバックが送信されてスプレッドシートにデータをためる

## 1. Slackに「今日の小技」を通知する
### 1. Slack Appを作成する
[Slack Appの管理画面](https://api.slack.com/apps)でサクッと作成します
![create slack app.png](https://qiita-image-store.s3.amazonaws.com/0/68146/272f2897-e595-66eb-5c8b-b771cab80030.png "create slack app.png")

### 2. Incoming Webhookを設定します
作成したSlack Appを選択して、通知したいチャンネルを選べばOKです。終わったら、自分のworkspaceにインストールします
![setup incoming webhook.png](https://qiita-image-store.s3.amazonaws.com/0/68146/621813ec-9b96-001f-de49-13d5694fc89a.png "setup incoming webhook.png")


### 3. GASでSlackに通知します
まずは「今日の小技」を書いたスプレッドシートを用意します
こんな感じ
![こんな感じ３.png](https://qiita-image-store.s3.amazonaws.com/0/68146/4ddfe5df-7bb9-ea37-5fbe-80086015f756.png "こんな感じ３.png")

次は、スプレッドシートのデータを取得して、Slackに通知します
（getColNumなどの関数は別途定義してます。[こちら](https://github.com/eversense/kowaza/tree/master/src)でソース公開してるのでそっちで確認してください ）

```
// SpreadSheetに記載された小技を日々通知する
function notify(){
  var row = getRowNumByDate(new Date())
  // 該当がなければスキップ
  if(row === 0){
    return;
  }
  
  var elements = getNotifyMessageElements(row);
  var title = getNotifyMessageForSlack(elements);
  var attachments = getNotifyAttachments(elements);

  sendToSlack(title, attachments)
}

function getNotifyMessageForSlack(e){
  return "*" + e.title + "*" + "\n\n" +
    e.description + "\n" +
    "by" + e.author;
}

function getNotifyAttachments(e){
    var actions = [
                {
                    "name": "feedback",
                    "text": "役に立った",
                    "style": "primary",
                    "type": "button",
                    "value": FEEDBACK_TYPES.GREAT
                },
                {
                    "name": "feedback",
                    "text": "使えない",
                    "style": "danger",
                    "type": "button",
                    "value": FEEDBACK_TYPES.BAD
                },
                {
                    "name": "feedback",
                    "text": "知ってた",
                    "type": "button",
                    "value": FEEDBACK_TYPES.KNOWN
                }
            ]
  return [
        {
            "fallback": "もう一回おしてね",
            "callback_id": e.id,
            "color": "#3AA3E3",
            "attachment_type": "default",
            "actions": actions
        }
    ]
}

function getNotifyMessageElements(row){
  var sheet = SpreadsheetApp.getActive().getSheetByName(TARGET_SHEET);
  var id = sheet.getRange(row, getColNum("id")).getValue()
  var title = sheet.getRange(row, getColNum("title")).getValue()
  var description = sheet.getRange(row, getColNum("description")).getValue()
  var author = sheet.getRange(row, getColNum("author")).getValue()
  
  return {id: id, title: title, description: description, author: author};
}

function sendToSlack(text, attachments){
  // username, channel, iconはSlack App側で設定したもので上書きされるので書かない
  var payload = {
                 "text": text,
                 "attachments": attachments,
                 "link_names": 1
                };
  
  var options = {
    "method" : "post",
    "payload" : JSON.stringify(payload)
  };

  UrlFetchApp.fetch(SLACK_WEBHOOK_URL, options);
}
```

で、 `notify` 関数を実行すると

![slackに通知.png](https://qiita-image-store.s3.amazonaws.com/0/68146/09a5d099-5a50-0236-f904-ede84d1bc2bb.png "slackに通知.png")

きました！ボタン付きのメッセージが！！！
先ほど、attachmentsのactionで定義したものがボタンになってます。

次は、これらのボタンを押したら、スプレッドシートに「役に立ったかどうか？」がたまるようにしてみましょう

## 2. ユーザーがボタンを押したらフィードバックが送信されてスプレッドシートにデータがたまるようにする
### 1. GASをウェブアプリケーションとして公開します
スプレッドシートにデータをためるには、GASをウェブアプリとして公開します。
こちらがよくまとまっているので参考にしてみてください。
[GASでwebアプリケーションを作る方法](https://tonari-it.com/gas-web-application-beginner/)

ウェブアプリとして公開すると`https://script.google.com/`から始まるurlが発行されるのでメモしておきます

### 2. Slack AppのInteractive Componentsを設定します
次に、Slack Appの `Interactive Components` ページにおいて、`Request URL`を設定します。urlは先ほど取得した`https://script.google.com/`から始まるurlです
![setup_spreadsheet_url.png](https://qiita-image-store.s3.amazonaws.com/0/68146/6cc1a2ea-88d1-fb48-31fb-be49aa203ce2.png "setup_spreadsheet_url.png")


### 3. エンドポイントとなるGASを作成します
最後に、GASを作成しましょう

```
function doPost(e){
  var json = JSON.parse(e.parameter.payload)
  addFeedback(json)
        
  
  var feedbackType = getFeedbackType(json.actions)
  var responsePayload = getResponsePayloadByFeedbackType(feedbackType);
  responseToSlack(json.response_url,responsePayload);
  
  // テキストを返すと送ったメッセージが上書きされてしまうので空を返す
  return ContentService.createTextOutput();
}

function addFeedback(json){
  var feedbackType = getFeedbackType(json.actions)
  var sheet = SpreadsheetApp.getActive().getSheetByName(TARGET_SHEET);
  var row = getRowNumById(json.callback_id);

  Object.keys(FEEDBACK_TYPES).forEach(function (key) {
    var col = getColNum(FEEDBACK_TYPES[key]);
    var range = sheet.getRange(row, col);

    if(FEEDBACK_TYPES[key] === feedbackType){
      appendName(range, json.user.name)
    } else {
      subName(range, json.user.name)
    }
  });
}

function appendName(range, name){
  var original_value = range.getValue()
  var names = original_value.split(",");
  names.push(name)
  var uniqueNames = names.filter(function (x, i, self) {
            return self.indexOf(x) === i && x !== "";
        });

  range.setValue(uniqueNames.join(","))
}

function subName(range, name){
  var original_value = range.getValue()
  var names = original_value.split(",");
  var index = names.indexOf(name);
  if(index >= 0){
    names.splice(index,1)
  }
  range.setValue(names.join(","))
}

function getFeedbackType(actions){
  var type = FEEDBACK_TYPES.KNOWN;
  if(actions.filter(function(e){ return e.name == "feedback" && e.value == FEEDBACK_TYPES.GREAT }).length > 0) {
    type = FEEDBACK_TYPES.GREAT;
  } else if(actions.filter(function(e){ return e.name == "feedback" && e.value == FEEDBACK_TYPES.BAD }).length > 0) {
    type = FEEDBACK_TYPES.BAD;
  }
  return type;
}

function getRowNumById(id){
  var sheet = SpreadsheetApp.getActive().getSheetByName(TARGET_SHEET);
  var range = sheet.getRange("A2:A");
  var values = range.getValues();
  var row = 0;
  for(i=0;i<values.length;i++){
    if(values[i][0] == id){
      row = i;
      break;
    }
  }
  return row + ROW_OFFSET;
}

function getResponsePayloadByFeedbackType(feedbackType){
  var payload = {
    "response_type": "ephemeral",
    "replace_original": false,
    "text": "text"
  }

  switch(feedbackType){
    case FEEDBACK_TYPES.GREAT:
      payload.text = "ありがとうございます。お役にたててよかったです :joy: ";
      break;
    case FEEDBACK_TYPES.BAD:
      payload.text = "ありがとうございます。明日に期待してください :smile: ";
      break;
    case FEEDBACK_TYPES.KNOWN:
      payload.text = "ありがとうございます。さすがです :laughing: ";
      break;
  }

  return payload;
}

function responseToSlack(responseUrl, payload){
  var options = {
    "method" : "post",
    "payload" : JSON.stringify(payload)
  };

  UrlFetchApp.fetch(responseUrl, options);
}
```

さぁ、準備は整いました。再度GASをウェブアプリとして公開し、Slack Appも再インストールしたら、`notify`を実行して、メッセージを送ります。そして、「知ってた」を押すと。。。
![ボタンを押したらフィードバック送信.png](https://qiita-image-store.s3.amazonaws.com/0/68146/0c613a12-2e00-9bb8-3ac7-1d975d6b72a3.png "ボタンを押したらフィードバック送信.png")

見事Slack Appから返信がきました！他のボタンを押すとまた違ったメッセージがかえってきます

そして、スプレッドシートをみてみると
![フィードバック取得.png](https://qiita-image-store.s3.amazonaws.com/0/68146/770f77f8-67be-2743-0eda-96898a25e1e8.png "フィードバック取得.png")
役に立ったか？のフィードバックも記録されてます！


## 最後に
めでたく毎日「今日の小技」がSlackに流れてきて、どれほど役に立ったのかを測るところまで仕組み化できました。
GASでは定期実行もできるので毎日流すことも可能です。githubでは最後に何人の役に立ったのかをSlackに投稿するところまで自動化しているのでみてみてください。

https://github.com/eversense/kowaza


そして、今回は業務として投稿してるので、宣伝です。
エバーセンスでは、技術で家族を幸せにしてくれる仲間を募集しています。よかったら応募してね。おまちしてます
https://www.wantedly.com/companies/eversense-co/projects