HTML5Experts.jp

Node.jsでSlack Command Botをつくってみよう

こんにちは。ごぶさたしています。以前の執筆から1年ちょっとになるのですが、その当時はInternet of Things(IoT)について書いたのですが、最近では市場がある程度まで到達したからでしょうか、それとも脆弱性の問題を問われることが多くなったせいでしょうか、話題は少し落ち着いてきたかに思われます。さて今ホットな話題は何でしょうか、ということで今回はChat Botsについて書いてみようと思います。

E-Commerceから Conversational Commerceへ

ここ最近話題になることが多いAIやBotsですが、私の周りではConversational interface、Conversational UXなどという言葉が去年からたびたび使われるようになっているようです。

これはAmazon Alexaなどのデバイスや、Facebook Messengerなどチャットアプリケーションなどの対話型テクノロジーをいかに活用しその使い勝手をよくするか、ということなのですが、必ずしもアプリケーションのUIデザインそのものを述べているわけではなく、既存のサービスを延長することを指していることも多いでしょう。例えば、今まではモバイル上のアプリケーションのみで車を呼べていたUberが、Facebook Messenger のチャットからも車をを呼べるような機能を加えたり、Slack上でTaco BellからタコスをオーダーできるTacoBot、というのもが挙げられます。

Slack Botを書いてみよう

さて、というわけで何かBotを書いてみたいと思いませんか?ここはNode.jsでSlack botを作成する方法を紹介したいと思います。

このチュートリアルでは、ディベロッパー向けのHTTPステータスコードのルックアップができるスラッシュ・コマンドを作ってみます。ここでは私が5年ほど前に何気なく作って、Mashableなどで紹介され思わぬ反響を得てしまったHTTP Status Catsを使ってみます。具体的には、Slack上でユーザが、/httpstatus [code] (例えば /httpstatus 404)と入力すると、そのステータスコードの意味と猫が一緒に表示される、という簡単なbotです。

まず試してみたい方は、HTTP Status Cats command for Slackを自分がアドミン権限のあるのチャットチームにインストールしてみてください。

さて、このチュートリアルは2つのパートに分けられます。

  1. スラッシュ・コマンドを書いて、自分のSlackチームにインストールする
  2. OAuthを使ってボットをSlack’s App Directoryなどで誰もがインストールできるようにする

とりあえず動くbotを書いてみたい、と思う方は1だけ試してみてで十分ですが、botをみんなにシェアしたい方は2も読んでみてください。

ソースコードと実際のボットのインストールボタンは両方GitHubにあります。では始めましょう!

1 プライベートなスラッシュコマンドボットの作成

ここで作るのは、Slackの公式な用語でいうところのCustom Integrationsというもので、自分のチャットグループ専用のプライベートなbot、もしくはいわゆるAppとして発表する前にドライ・ランを行うことを指します。アカウントを持っていない方はまずサインアップしてから始めましょう。

1.1 スラッシュコマンドの設定

ログインして、my.slack.com/services/new/slash-commandsでコマンドを選びます。ここでは/httpstatusと入力しAdd Slash Command Integrationボタンをクリックして次のステップへ進みます。

Tokenなどの欄がありますが現時点では、(1) Command、 (2) URL、 (3) Method、の3つが必要になります。

(1)には、/httpstatus、(3)には、POST、そして(2)のURLは次のように設定してください。

開発中に使用するURLを取得するにはngrokを使ってみましょう。いろいろなツールがあるのですが、これは自分のローカルホストをパブリックURLとしてトンネルできるというとても便利なツールなので私のイチオシです。開発途中にデプロイすることなく、Webhookが手軽に使えます。自分のローカルホスト、たとえば http://localhost:3000 をつかったままOAuthのテストもできるのです。(注:よく聞かれるのですが、ngrokはあくまでも開発ツールですのでプロダクションには適していません。デプロイメントに関しては最後の章を読んでください)

https://ngrok.comから自分のマシンにngrokをインストールしたら、ターミナルで自分の使いたいポート番号(このチュートリアルでは 3000)を設定します。

$ ngrok http 3000

すると下のスクリーンショットのように、Forwardingアドレスが取得できるので、そのURL(例えばhttps://71f03962.ngrok.io)をSlackセッティングの、上のスクリーンショットで示された(2)の欄で使います。

すべての設定が終えたらSaveボタンを押します。”Your settings have been saved!”のメッセージが画面上部に現れるのを確認してください。

1.2 Node.js を使ってレスポンスを書く

基本的にbotは、ユーザがSlackインターフェイス上でコマンドを実行した際HTTP POST(または設定次第では GET)によって指定先のURLにメッセージが届け、プログラムでその応答をユーザに返す、という作業になります。

たとえばそのユーザが/httpstatus 302というコマンドを送信した場合、指定URLに送られるデータは次のようになります。

command=/httpstatus
text=302
response_url=https://hooks.slack.com/commands/1234/5678
…

Botは、これに対する応答をユーザに返します。この場合はユーザが尋ねているステータス302の定義とこの猫を返しましょう。

ではそのコードを書いてみましょう。

まず、Express.jsbody-parserをインストールします。

$ npm install express body-parser --save

index.jsで、expressのインスタンスを作り、先ほどngrokで設定したポート番号、3000でサーバを始動します。

'use strict';
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

const server = app.listen(3000, () => { console.log('Express server listening on port %d in %s mode', server.address().port, app.settings.env);});

次はHTTP POSTルートメソッドで、コマンドを扱います。

app.post('/', (req, res) => {
 let text = req.body.text;
 // ここでbotを書きます
});

ここでtextの値を取得します。HTTP Status botの場合、/httpstatus コマンドの値、例えば”404″が textの値になります。同時に、ユーザが数字以外を入力した際にエラーメッセージを送るなどのエラーチェックもしておきましょう。

if(! /^\d+$/.test(q.text)) { // not a digit
 res.send('Error: enter a valid status code, such as 200');
return; }

このエラーは、ユーザだけにプライベートに送信されるメッセージでチャットそのものには表示されません。

エラーがない場合は、コマンドに対する応答をJSONとしてレスポンスします。

let data = {
 response_type: 'in_channel', 
 text: '302: Found',
 attachments:[{
   image_url: 'https://http.cat/302.jpg'
 }]
};
res.json(data);

response_typein_channelとすることで応答はチャットメンバー全員に見えるように送信されます。デフォルトはその逆のephemeralで、コマンドを送ったユーザのみに表示されます。

このコマンドと応答は次のようになります。

このサンプルコードでは、応答をわかりやすくハードコードで示してありますが、実際はストリングなどは別のファイルに定義しています。下のスクリーンショットのように存在しないHTTPステータスに対してのエラーメッセージも定義しましょう。実際のコードはソースコードを参照してください。

ディスプレイはボーダー色などのカスタマイズが可能です。詳しくはSlackドキュメンテーションのBasic message formattingを参照してください。

次のステップでは、このボットを自分のチャットグループ以外に配布するために必要な認証とコードのデプロイについてです。

2. Slack Botのディストリビューション

この「Custom Integration」をインストール可能な「App」にするには、コードのデプロイをして他のチャットにもインストールできるようにせねばならないのですが、そのためにはあといつくかのステップが必要になります。

2.1 Appセットアップ

まず、自分のAppを申請し、クライアントIDやシークレットなどのクレデンシャルを取得します。https://api.slack.com/appsCreate an Appボタンをクリックしてください。

このフォームにはいくつもの欄があり少しわかりづらいのですが、スラッシュコマンドのbotには次の3つが最低必要になります。

2.1.1 API Keyを.envファイルに保管

ここで取得したClient IDClient secretVerification token.env ファイルに別に保管してbotのメインのコードから切り離すことを推奨します。gitを使う場合は、このファイルを .gitignore ファイルに付け加えるのを忘れずに。

SLACK_CLIENT_ID=12345XXXXX.09876XXXXX 
SLACK_CLIENT_SECRET=535d2f9....
SLACK_VERIFICATION_TOKEN=42P829U...

2.1.2 Foremanを使う

他にも手段はありますが、Herokuにデプロイするために私はNode Foremanを使っています。Foremanを使うには、npmを使ってglobalフラッグでインストールしてください。

$ npm install -g foreman

アプリケーションの Root に .procfile を作成し、この一行を加えます。

web: node index.js

index.jsを実行するには node index.jsの代わりに次のコマンドを使います。

$ nf start

2.2 ユーザの認証

Slackは認証にはOAuthを使っています。実際には自分でOAuthを実装しなくても、Slack ボタンを使えば簡単に認証できるようになっています。

公式のドキュメンテーションのダイアグラムに手を加えて、流れをわかりやすくするためにGIFアニメーションにしてみました。

ここでの実際のフローは次のようになります。

  1. ウェブページを作成し、認証ボタンを置く。ユーザがボタンをクリックするとパラメータがSlackに送信される(ユーザは認証ページにリダイレクトされる)。
  2. Node appには、SlackからGETで10分だけ有効な仮のコードが送られる。
  3. アクセストークンを得るために oauth.access API使い認証コードをPOSTする。Node app側から`200 OK`を受け取り次第、このプロセスが完了。
  4. オプションとして、このトークンを使ってSlackの他のAPIにもアクセス。例えば、認証後、ユーザをhttps://team-name.slack.comにリダイレクトするなど。

2.2.1 Slack ボタンの設定

Slackボタンを使うには、まずウェブページを作成してください。私の場合はこのNode Appとは切り離した別のHTMLページを作成し、GitHub Pagesにホストしました。

次にボタンを設定しましょう。 https://api.slack.com/docs/slack-button に行き、Add the Slack button までスクロールして下さい。

このボタン作成ツールのCommendsのチェックボックスをチェックします。

上で示したフローの4を実行したい場合は、このGETパラメータを下のように変更します。

<a href="https://slack.com/oauth/authorize?scope=commands+team%3Aread&client_id=your_client_id">

ここでscopeに着目してみてください。commandsの他にteam:read(コロンは%3Aとエスケープ)が必要になります。詳しくはOAuth scopes on the Slack API docsで。

2.2.2 トークンの発行

さて、Nodeコードに戻りましょう。仮のコード(req.query.code)をGETで取得するためにまたExpress.jsを使います。

何でもよいのですがここでは/slack routeを使いましょう。この場合ngrokのURL は http://71f03962.ngrok.io/slack のようになります。Slack App設定ページ(https://api.slack.com/apps/YOUR_APP_ID/oauth)の、OAuth & Permissions セクションにある、Redirect URLの欄にはこのURLを設定してください。

仮のcodeを取得されたら、それを自分のAPIクレデンシャルとともにPOSTで送って、トークンと交換します。POSTするためにここではNode.jsのHTTPリクエストクライアントである、Requestを使いましょう。

$ npm install request --save

仮のcodeを取得し、それをtokenと交換するコードが下になります。

const request = require('request');

app.get('/slack', function(req, res){ let data = {form: { client_id: process.env.SLACK_CLIENT_ID, client_secret: process.env.SLACK_CLIENT_SECRET, code: req.query.code }};

request.post('https://slack.com/api/oauth.access', data, function (error, response, body) { if (!error && response.statusCode == 200) { // おしまい! // ここからはオプションでチーム情報を取得 let token = JSON.parse(body).access_token; // Auth token } ...

2.2.3 オプショナル: ユーザをチームURLにダイレクトする

認証が済んだらそこで終えてもよいのですが、この画面でユーザを置き去りにするのはあまりよいUXとはいえないので、チームページにリダイレクトしてみましょう。

リダイレクトURLのサブドメインとなるチーム名はteam.infoAPIで取得できます。

このAPIを使うにはトークンが必要なので前述のコードでの、トークンにアクセスする箇所に下のコードを追加します。

...
request.post('https://slack.com/api/team.info', {form: {token: token}}, function (error, response, body) {
 if (!error && response.statusCode == 200) {
   let team = JSON.parse(body).team.domain;
   res.redirect('http://' +team+ '.slack.com');
 }
});

これで API からチーム名(team.domain)が返されました。最終的にこれを使ってチームURLにリダイレクトしてできあがり!

このチュートリアルでは簡素化したコードを使いましたが、全ソースコードはGitHubで見てみてください。

2.3 サーバにデプロイする

最後にデプロイしておしまいです。APIクレデンシャルのenv vars設定を忘れないように!私はHerokuを使っているのですが、Herokuの場合、heroku configコマンドを使います。例えば、heroku config:set API_KEY=123456というふうに設定してください。

Slackの設定で画面で指定したngrok URLを、デプロイ先のURLに変更するのもお忘れなく。

さて、プロセスが少し面倒ですが、コード自体は簡単だったと思います。もし何か面白いボットを作った際にはぜひ教えてくださいね!