Slash CommandsとGASでSlackのオリジナルコマンドをつくる

[Progate Advent Calendar 2017](https://qiita.com/advent-calendar/2017/progate) 21日目です！

今回は、SlackのSlash CommandsとGAS(Google Apps Script)を使って、
Slackで使えるオリジナルコマンドをつくる方法を紹介します！

# Slash Commands
Slash Commandsというのは、その名の通り、`/`から始まるコマンドのことです。
標準で用意されているものだと、`/remind`があります。けっこう便利でよく使っています。

<img width="671" alt="remind command" src="https://qiita-image-store.s3.amazonaws.com/0/172126/19452481-bdf3-2249-209a-35bc43d374ee.png">

# Google Apps Script
Google Apps Script (GAS)は、JavaScriptをベースにしたスクリプト言語です。
JavaScriptが書ければ普通に扱えます。ただ、ES6の記法には対応していません。

GASの魅力は2つあります。
1つ目は、Googleのサービスと連携ができることです。これは今回活用しないので説明は割愛します。
2つ目は、Googleがサーバー環境を**無料で**提供してくれることです。
WebAPIとして公開したり、cronのように時間主導型のトリガーを設定したりすることができます。

ケチなので、個人的には**無料**ってところに魅力を感じます。。

# 今回つくるもの
登場人物の紹介は済んだので、早速オリジナルコマンドをつくっていきます！
今回は、NetatmoのAPIを使って現在のCO2濃度や湿度を確認できるコマンドをつくります。

具体的には、`/netatmo co2`と入力すると、CO2濃度を教えてくれるコマンドです。
<img width="470" alt="slack command co2" src="https://qiita-image-store.s3.amazonaws.com/0/172126/54ef5943-c7ea-44dc-0b79-1864cea0bc1f.png">

Netatmoって何？という方は、[3日目の記事](https://qiita.com/chikuwa111/items/7d9d3e9bacbcb860e8b1)をご覧ください。
(もし読んでくださったら「いいね」もお願いします笑)

Netatmoと言っても今回使うのはただのWebAPIです。
Netatmo持っていないよーという方でも参考にしていただけると思います！
スモールステップで進めていくので長くなると思いますが、よろしければ最後までお付き合いください！

# オリジナルコマンドの作成

## a. 流れを理解する
実際につくり始める前に、どのように登場人物が活躍するか確認しましょう。
図で示すと以下のようになります。

<img width="557" alt="slack gas flow" src="https://qiita-image-store.s3.amazonaws.com/0/172126/11c63246-64dc-73cd-331b-6d9cf4ca126b.png">

NetatmoのAPIは4番の中で使われる、というイメージです。
この画像はこれから何回か登場するので、覚えなくて大丈夫です！

## b. GASのプロジェクトを作成する
流れが理解できたら、まずコマンドを受け付けるサーバー側を用意します。

https://script.google.com を開いてみましょう。
Googleアカウントにログインしている状態であれば、新規のプロジェクトがつくられるはずです。

<img width="649" alt="google apps script new project" src="https://qiita-image-store.s3.amazonaws.com/0/172126/2e6528bc-4690-22c5-09ed-0540d9e1914f.png">

ログインしていない場合は、Google Apps Scriptのページに飛ぶと思います。
その場合は「Start Scripting」ボタンから進めば大丈夫です！

画像からわかるように、GASはブラウザ上のエディターで開発します。
まずは簡単にログを出力してみましょう。

```javascript:コード.gs
function myFunction() {
  Logger.log('console.logじゃないよ！');
}
```

これで保存します。
保存するときにプロジェクト名を聞かれますが、今回はNetatmoAppにしました。
ちなみに、このプロジェクトはGoogleドライブのマイドライブ直下に保存されます。

関数の実行は、動画の再生ボタンみたいなボタンでできます。
また、ログは、メニューの「表示」=>「ログ」から確認できます。

<img width="819" alt="gas log check" src="https://qiita-image-store.s3.amazonaws.com/0/172126/8ce68a0b-0aa1-65d3-1330-143f66ed1877.png">

これでもうGAS初心者は卒業です！

## c. POSTのリクエストを受けられるようにする
GAS初心者も脱したところで、早速GASでPOSTのリクエストを受けられるようにしましょう！
画像での3番に当たります。

<img width="557" alt="slack gas flow 3" src="https://qiita-image-store.s3.amazonaws.com/0/172126/b29af7e9-a68c-b661-3c96-df1e3525ecb6.png">

GASでは、**doPost**という関数でPOSTのリクエストを受けるようになっています。
まずはdoPost関数を定義しましょう。

```javascript:コード.gs
function doPost(e) {
}
```

引数`e`にパラメータなどが入ってきます。

ついでに4番、メッセージの用意もしてしまいましょう。
メッセージはJSONで用意します。

```javascript:コード.gs
function doPost(e) {
  var response = { text: 'テストメッセージ' };
  
  return ContentService.createTextOutput(JSON.stringify(response)).setMimeType(ContentService.MimeType.JSON);
}
```

returnのところが少し複雑ですが、このようにすることでJSONを返せます。

最後に、このプロジェクトをWebアプリケーションとして公開します。
メニューの「公開」=>「ウェブ アプリケーションとして導入...」のように進みます。

<img width="663" alt="release gas" src="https://qiita-image-store.s3.amazonaws.com/0/172126/835c296d-35ed-52a3-95af-6e8d3c88c427.png">

そうするとダイアログが出てきます。
画像のように設定をしましょう。
3つ目の**アプリケーションにアクセスできるユーザー**はデフォルトでは**自分**になっています。
忘れずに変更しましょう。

<img width="481" alt="release gas dialog 1" src="https://qiita-image-store.s3.amazonaws.com/0/172126/eb40a8ab-f361-5e8a-6890-5798f5796b58.png">

これで「導入」ボタンを押せば見事Webアプリケーションとして公開されます！
次のダイアログで出てくるURLは取っておいてください。あとで必要になります。

<img width="477" alt="release gas dialog 2" src="https://qiita-image-store.s3.amazonaws.com/0/172126/647ad625-0d19-aeb0-ea6c-f29fef6cc65f.png">

ここまでで、画像の3と4の部分を簡単にですが実装することができました！

## d. Slack Appをつくる
さて、先に画像の3番と4番を実装してしまいましたが、ここで画像の1番の用意をします。
ここまででひとまずコマンドが正常に実行できるようになります！

<img width="557" alt="slack gas flow 1" src="https://qiita-image-store.s3.amazonaws.com/0/172126/ed26c510-f84d-0e81-e7db-2360a5894834.png">

まずは https://api.slack.com/apps にアクセスしましょう。
Slackのログインができていなければログインします。
ログインできていれば、右側に「Create New App」というボタンが表示されるはずです。
そこからアプリをつくりましょう！

<img width="774" alt="slack create new app" src="https://qiita-image-store.s3.amazonaws.com/0/172126/4a32d409-0180-c40e-5e9d-e85fe0f08320.png">

今回はApp NameをNetatmoAppにします。
Workspaceは、コマンドを使えるようにしたいWorkspaceを選択します。
入力できたら「Create App」でAppを作成します！

Appが作成できたら、Settings画面に遷移します。
はじめに、どの機能を使うか選びます。
いろいろ並んでいますが、今回は**Slash Commands**です。

<img width="985" alt="slack app setting slash commands" src="https://qiita-image-store.s3.amazonaws.com/0/172126/88e45a4e-c3a8-0a97-02c9-beaab1650426.png">

次に「Create New Command」で新しくコマンドを作成します。
今回はコマンドを**/netatmo**にします。
リクエストURLは、GASを公開したときに取っておいたURLにします。
短い説明や使い方のヒントは、コマンド入力時に表示されます。
このフォームの下のプレビューで確認しながら設定してみましょう。

<img width="544" alt="create new command" src="https://qiita-image-store.s3.amazonaws.com/0/172126/7187751d-1dca-1028-af4d-631ad84c9829.png">

コマンドがつくれたら、SettingsのBasic Informationに戻ります。
そうすると「Add features and functionality」にチェックが付いているはずです。

<img width="999" alt="basic information" src="https://qiita-image-store.s3.amazonaws.com/0/172126/cd089c52-d50b-1de3-39f5-0c349e194cab.png">

できていたらAppをworkspaceにインストールします！
ボタンを押して、遷移先で承認ボタンを押せば完了です！

<img width="483" alt="authorize slack app" src="https://qiita-image-store.s3.amazonaws.com/0/172126/4ece1312-a34c-5ad5-9faf-fa190d2d064b.png">

注意: workspaceの管理権限を持っていない場合は、管理権限のあるユーザーの承認が必要になります。

インストールが完了すれば、もうコマンドが使えるようになっているはずです！
Slackで`/netatmo`と実行して、メッセージが返ってくるか試してみましょう。
テストメッセージが返ってくればうまくいっています！

<img width="968" alt="netatmo test message" src="https://qiita-image-store.s3.amazonaws.com/0/172126/0e8bcab5-cb6d-028c-de6a-b11c2e88c3e4.png">

ここまでで一通りコマンドが動くようになりました！
Slack App側の設定はこれでおしまいです。

## e. パラメータを使う
ここからはGASに戻ります。
次は、コマンドの入力をGASで受け取って、それに応じてメッセージを変えてみます。
現状のGASのコードは以下のようになっています。

```javascript:コード.gs
function doPost(e) {
  var response = { text: 'テストメッセージ' };
  
  return ContentService.createTextOutput(JSON.stringify(response)).setMimeType(ContentService.MimeType.JSON);
}
```

引数`e`にパラメータが入っていると前に述べました。
結論から言いますと、`e.parameter.text`にコマンドの中身が入っています。
試しにオウム返しさせてみましょう。

```javascript:コード.gs
function doPost(e) {
  var command = e.parameter.text;
  var response = { text: command };
  
  return ContentService.createTextOutput(JSON.stringify(response)).setMimeType(ContentService.MimeType.JSON);
}
```

GASはコードを更新したら、再度公開設定をする必要があります。
メニューの「公開」=>「ウェブ アプリケーションとして導入...」ですね。

注意ですが、**プロジェクト バージョン**を**新規作成**にする必要があります。
これは更新する度に必要ですので、忘れないようにしましょう。

<img width="470" alt="update gas" src="https://qiita-image-store.s3.amazonaws.com/0/172126/971b5342-e262-6b92-44ab-97b915318c25.png">

更新してもURLは変わらないので、早速Slackで試してみましょう。

<img width="968" alt="slack parroting" src="https://qiita-image-store.s3.amazonaws.com/0/172126/0da13f54-2e08-a6c1-c54a-726c1a76e041.png">

画像のように、`/netatmo`は含まれずにメッセージが返ってくると思います。
コマンドの入力を取り出したい場合は、`e.parameter.text`を使いましょう！

もう一点、パラメータ関連でやっておくべき項目があります。
それは**Verification Token**のチェックです。

Slack Appには、Verification Tokenというものがあります。
このTokenはパラメータで渡ってくるので、この値を確認することで、
「このリクエストは自分がつくったAppから送られたものだ！」というチェックができます。

Verification TokenはAppのBasic Information画面で確認できます。

<img width="1031" alt="verification token" src="https://qiita-image-store.s3.amazonaws.com/0/172126/a55fe838-541f-2648-5c59-d6e84d0b4256.png">

Tokenが確認できたら、GASでこのTokenをチェックするコードを書きましょう。
Tokenは`e.parameter.token`で取得できます。

```javascript:コード.gs
function doPost(e) {
  var verificationToken = e.parameter.token;
  if (verificationToken != 'XXXXXXXXXX') { // AppのVerification Tokenを入れる
    throw new Error('Invalid token');
  }

  var command = e.parameter.text;
  var response = { text: command };
  
  return ContentService.createTextOutput(JSON.stringify(response)).setMimeType(ContentService.MimeType.JSON);
}
```

このようにすることで、期待しないリクエストにはエラーを返すことができます。
更新する際は公開設定し直すことを忘れずに！

# 休憩
ここまでで、GASでSlash Commandsをつくる基礎を紹介しました。
もう、自分の思い通りのオリジナルコマンドをつくる土台は整っています！
あとは、

-  [GASのリファレンス](https://developers.google.com/apps-script/reference/base/)
-  [Slackのメッセージのフォーマット](https://api.slack.com/docs/message-formatting)

を読みながら、理想のオリジナルコマンドをつくりましょう！

長くなってしまったのでここで休憩です。
ここから先は、オリジナルコマンドの一例として、はじめに紹介したAppをつくっていきます。
よろしければ引き続きお付き合いください。

# NetatmoAppを完成させる

## a. NetatmoのAPIを確認する
今回はNetatmo Weather StationのGetstationsdataというAPIを使います。
Developer登録やApp登録など必要になりますが、こちらは割愛します。

APIの仕様は以下のようになっています。Responseは今回使うものだけ抜粋しました。

**Request**
`GET https://api.netatmo.com/api/getstationsdata?access_token=[TOKEN]`

**Response**

```javascript

{
  body: {
    devices: [
      {
        dashboard_data: {
          CO2: 600,
          Humidity: 30,
        },
      },
    ],
  },
}
```

## b. NetatmoのAPIにリクエストを送る
APIの確認ができたので、GASでデータを取って、CO2濃度をメッセージとして返してみます。
GASでリクエストを送るときは`UrlFetchApp`というものを使います。

```javascript:コード.gs
function doPost(e) {
  var verificationToken = e.parameter.token;
  if (verificationToken != 'XXXXXXXXXX') { // AppのVerification Tokenを入れる
    throw new Error('Invalid token');
  }

  var accessToken = 'XXXXXXXX'; // NetatmoのAccess Tokenを入れる
  var url = 'https://api.netatmo.com/api/getstationsdata?access_token=' + accessToken;

  var response = UrlFetchApp.fetch(url);
  var json = JSON.parse(response.getContentText());
  var dashboardData = json.body.devices[0].dashboard_data;
  var co2Data = dashboardData.CO2;

  var response = { text: 'CO2濃度は' + co2Data + 'ppmです！' };
  
  return ContentService.createTextOutput(JSON.stringify(response)).setMimeType(ContentService.MimeType.JSON);
}
```

これを公開するときには、外部サービスへの接続の許可をする必要があります。

<img width="637" alt="allow request" src="https://qiita-image-store.s3.amazonaws.com/0/172126/911a333b-228e-49ad-4152-d09d12e63bf8.png">

実行してみると以下のようになります。うまく値が取れています！

<img width="470" alt="slack command co2" src="https://qiita-image-store.s3.amazonaws.com/0/172126/54ef5943-c7ea-44dc-0b79-1864cea0bc1f.png">


## c. コマンドによってメッセージを変える
仕上げに、コマンドによってCO2の値を返すか湿度の値を返すか変えるようにします！

```javascript:コード.gs
function doPost(e) {
  var verificationToken = e.parameter.token;
  if (verificationToken != 'XXXXXXXXXX') { // AppのVerification Tokenを入れる
    throw new Error('Invalid token');
  }

  var accessToken = 'XXXXXXXX'; // NetatmoのAccess Tokenを入れる
  var url = 'https://api.netatmo.com/api/getstationsdata?access_token=' + accessToken;

  var response = UrlFetchApp.fetch(url);
  var json = JSON.parse(response.getContentText());
  var dashboardData = json.body.devices[0].dashboard_data;
  
  var command = e.parameter.text;
  var text = '';

  if (/co2/i.test(command)) {
    var co2Data = dashboardData.CO2;
    text = 'CO2濃度は' + co2Data + 'ppmです！';
  } else if (/humidity/i.test(command)) {
    var humidityData = dashboardData.Humidity;
    text = '湿度は' + humidityData + '%です！';
  } else {
    text = '正しくコマンドが指定されませんでした';
  }

  var response = { text: text };
  
  return ContentService.createTextOutput(JSON.stringify(response)).setMimeType(ContentService.MimeType.JSON);
}
```

これで、`/netatmo co2`で実行されたらCO2の値を、
`/netatmo humidity`で実行されたら湿度の値を返すようになりました。
また、コマンドを間違えたときはそれが分かるようなメッセージを返すようにしています。

<img width="438" alt="command failed" src="https://qiita-image-store.s3.amazonaws.com/0/172126/d70d6f76-035b-4980-1703-869baa19c6f0.png">

これで完成です！

# 終わりに
Slash CommandsとGASでオリジナルコマンドをつくってみました。
GASはGoogleスプレッドシートなどのサービスと連携ができるのも強みです。
これを活用できれば、さらにできることの幅が広がります！

個人的にはBotやInteractive Componentにも挑戦したいと思っています！

皆さんもGoogle先生に感謝しながら、オリジナルコマンドをつくってみてください！
最後までお読みいただきありがとうございました！

# おまけ
## a. GASが間違っていたとき
GASに不備がある状態でコマンドを実行するとhtmlらしきものがずらずらっと出力されます笑
でもその中にエラーメッセージが含まれているので問題解決に役立ちます。

<img width="973" alt="gas error" src="https://qiita-image-store.s3.amazonaws.com/0/172126/6c4947f2-7acf-d7dd-d3d2-4a74f06f3f24.png">

## b. 実際に動かしているNetatmoAppの紹介
今回つくったのは、実際に動かしているNetatmoAppの簡易版でした。
本物のNetatmoAppは、複数同時に出力できたりフォーマットが整っていたりします。

<img width="514" alt="real netatmoapp" src="https://qiita-image-store.s3.amazonaws.com/0/172126/76071ddb-d073-fb86-3d1b-b956c14257b8.png">