SlackのBotkitでbuttonとinteractive messagesを使ってみた

#SlackのBotkitでbuttonとinteractive messages
初めてJavaScriptとSlackを触って簡単なボットを作ってみたので、備忘録としての投稿です。
4月にプログラミングを始めたので理解できていない点が多いと思います。Slack関連の日本語の投稿が少ないので、本家の英語の各種説明を読むよりはマシ、という方の参考になれば嬉しいです。

##環境
- Node.js(6.4.0)
- Heroku

##初めに

ボタンなどのインタラクティブな機能を実装するにはSlack Appの登録が必要です。
Slackのローカルから動かせるカスタムインテグレーションとしてのUser Botでは使えないので注意してください。
またhttps通信を行うので今回はHerokuを使うことを前提に書いていきます。

基本的には
[Welcome to the Slack API](https://api.slack.com/slack-apps)
に細かく書いてあることを基に行っていきます。



##Slack App登録
**Create App**

[Create App](https://api.slack.com/apps/new)で
- App Name
- Team
- Description
- Describe what your app does on Slack (3-5 sentences)
を入力して登録。

次に、移った先の左側の欄にある`App Credentials`、`Bot User`、`Interactive Messages`を設定します。

***
**App Credentials**

`Client ID`と`Client Secret`は後で必要です。

`Redirect URI(s)`は、
>`https://{your app}.herokuapp.com/oauth`
と入力します。

***
**Bot User**

ボットを自分のチャンネルへ呼び出すときに使用するボットの名前を設定します。
　

***
**Interactive Messages**

`Request URL`には、
>`https://{your app}.herokuapp.com/slack/receive`
が入ります。


以上でSlackでの設定は終わりです。

##コード
Heroku上で動かすためには、
- `main.js`
- `Procfile`
- `package.json`
の３つのファイルが必要です。

`main.js`に関しては[公式のデモ](https://github.com/howdyai/botkit/blob/master/examples/slackbutton_bot_interactivemsg.js)もありますが個人的には非常にわかりにくかったため、動かす上で不可欠なもの(だと僕が思ったもの)だけを抜粋して諸々付け加えたコードを載せます。



```main.js
/* Uses the slack button feature to offer a real time bot to multiple teams */
const querystring = require("querystring");
const Botkit = require("botkit");
const os = require("os");

// Heroku上で動かすため、port指定の箇所を !process.env.port から !process.env.PORT に変更
if (!process.env.clientId || !process.env.clientSecret || !process.env.PORT) {
  console.log('Error: Specify clientId clientSecret and port in environment');
  process.exit(1);
}

var controller = Botkit.slackbot({
  // interactive_replies: true, // tells botkit to send button clicks into conversations
  json_file_store: './db_slackbutton_bot/',
}).configureSlackApp(
  {
    clientId: process.env.clientId,
    clientSecret: process.env.clientSecret,
    scopes: ['bot'],
  }
);

controller.setupWebserver(process.env.PORT,function(err,webserver) {
  controller.createWebhookEndpoints(controller.webserver);

  controller.createOauthEndpoints(controller.webserver,function(err,req,res) {
    if (err) {
      res.status(500).send('ERROR: ' + err);
    } else {
      res.send('Success!');
    }
  });
});

// just a simple way to make sure we don't
// connect to the RTM twice for the same team
var _bots = {};
function trackBot(bot) {
  _bots[bot.config.token] = bot;
}

controller.on('create_bot',function(bot,config) {

  if (_bots[bot.config.token]) {
    // already online! do nothing.
  } else {
    bot.startRTM(function(err) {

      if (!err) {
        trackBot(bot);
      }

      bot.startPrivateConversation({user: config.createdBy},function(err,convo) {
        if (err) {
          console.log(err);
        } else {
          convo.say('I am a bot that has just joined your team');
          convo.say('You must now /invite me to a channel so that I can be of use!');
        }
      });
    });
  }
});

// Handle events related to the websocket connection to Slack
controller.on('rtm_open',function(bot) {
  console.log('** The RTM api just connected!');
});

controller.on('rtm_close',function(bot) {
  console.log('** The RTM api just closed');
  // you may want to attempt to re-open
});


// ========================================================================================================================================================================
// 以下 リアクション定義

controller.hears("hi", "direct_message,direct_mention,mention", (bot, message) => {

  bot.reply(message, {
    "text" : "hello"
  });

});


controller.hears("how are you", "direct_message,direct_mention,mention", (bot, message) => {

  bot.reply(message, {
    "text": "Great, you?",
    "attachments": [{
      "fallback": "Couldn't reply.",
      "callback_id": "greeting",
      "attachment_type": 'default',
      "actions": [
        {
          "name": "good",
          "value": "good",
          "text": "Pretty Good",
          "type": "button"
        },{
          "name": "bad",
          "value": "bad",
          "text": "Not so good",
          "type": "button"
        }
      ]
    }]
  });

});


controller.on('interactive_message_callback', function(bot, message) {

  if (message.callback_id == "greeting") {

    const name = message.actions[0].name;
    const value = message.actions[0].value;

    var text = ""

    if (name == "good") {
      text = "That's good."
    } else {
      text = "What's wrong?"
    }

    bot.replyInteractive(message, {
      "text": text
    });
  }

});

```

```txt:Procfile
web: clientId={your slack app ID} clientSecret={your slack app pw} node main.js
```

```package.json
{
  "name": "slack-bot-botkit",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "engines": {
    "node": "6.4.0"
  },
  "author": "",
  "license": "ISC",
  
  "dependencies": {
    "botkit": "^0.2.2",
    "querystring": "^0.2.0"
  }
}

```
##デプロイ

最低限ではありますが一応この３つのファイルだけで動くので、試しに自分のHerokuへデプロイしてみます。

Herokuへのデプロイについては、[Heroku：(Application Errorにならないための)Node.jsアプリのデプロイ入門](http://qiita.com/oden/items/9341b672917afb43a817)の記事に詳しくあります。

Herokuへのデプロイ後は、ブラウザから
`https://{your app}.herokuapp.com/login`

にアクセスして自分のチームで動かすための認証を行います。

作ったボットはチームのチャット画面で生きているので、ダイレクトメッセージで指定のキーワードを送ればボタン付きで反応してくれます。

##メッセージの形式

`bot.reply`、`bot.replyInteractive`が取る引数のオブジェクトの形式については、

[Basic message formatting](https://api.slack.com/docs/message-formatting)（基本的な形式）
[Making messages more interactive with buttons](https://api.slack.com/docs/message-buttons)（ボタン用の形式）

に詳しく載っています。また今度翻訳してみようと思います。

##おわり

備忘録としてではありますが、初めて投稿したので一瞬で疲れてしまい割と大事なところを割愛してしまった気がします。次は(?)もう少し頑張りたいと思います…