本記事はDan PrinceとMatthew Wilkinが査読を担当しています。最高のコンテンツに仕上げるために尽力してくれたSitePointの査読担当者の皆さんに感謝します。
Slackの存在感はいまや絶大で、開発者と非開発者コミュニティの両方にとても多くのユーザーを抱えています。なめらかなユーザーインターフェイス、「チーム」と「チャネル」というコミュニケーションのコンセプト、生産性向上のための数々のサービスとの同期(Dropbox、Box、Googleカレンダー、Hangoutsなど)、Giphyやリマインダー機能などによって、使うのが楽しくなるのです。また、開発者はSlackのAPIを使うことで、これらの機能を拡張したり、チーム専用の独自の使い勝手を実現したりできます。
「そんなのSlack独自のものじゃないよ。HipChatやほかのアプリだって同じような機能があるじゃないか」と思うかもしれませんが、詳しくはこちらhttp://slackvshipchat.com/をご覧ください。
この記事の目的
この記事の狙いは、シンプルなNodeアプリを使い、自分のSlackチャンネルをカスタムコマンドラインターミナルへと進化させること。slack-terminalize※という、メッセージ初期処理を簡潔にまとめてくれるヘルパーモジュラーを使用します。slack-terminalizeはSlackのReal-Time API Node clientを使っていて、botがこちらの要求を聞いて動作するように準備を整えてくれます。
※「slack-terminalize」を使用した結果については、一切の責任を負いません。
今回はSlash Commandsを使わずに、通常のメッセージをコマンドとして解釈します。そのため、Slash Commandsについて詳しく知りたい人におすすめする記事ではありません。
始める前に
この記事はJavaScriptやNode.jsについてのある程度の知識があり、チーム、チャネル、bot、インテグレーションといったSlack用語に慣れていることを前提としています。nodeとnpmをインストールしておいてください。開発環境を設定するには、『SitePoint introduction to npm』を参照してください。
slack-terminalize開発動機
自然言語クエリに反応するHubotのScriptsには良いものがたくさんありますが、Linux愛好家であれば誰でもうなずく通り、やりたいことのたいていは短いコマンドを打ち込み、キーボードを叩くだけで完了できます。コマンドが単純なのはモバイル端末では特に大切で、少ないタイピングで多くのことができます。コマンドラインシステムを考えるなら、シェルの実行時間のほとんどは、フェッチング、構文解析、トークン化、コマンドのディスパッチ(単純化しすぎではありますが)といった、とても骨の折れる大仕事をしているのです。
そう考えると、まったく同じことができるモジュールが必要だと感じました。Slackチャネルのためのシェル、とも言えるでしょう。プロセスとディスパッチというアプローチと、プラグインのようなアーキテクチャーを採ることで、カスタムコマンドを追加するだけでなく、Slack-terminalizeによる、アプリの動作を定義することに集中できます。
実際に始めてみましょう
新しいbotユーザーを作ります。https://<your-team-name>.slack.com/services/new/botを開き、ユーザーネームを選んでAdd Bot Integrationをクリックします。
botがチャネルに動作できるようにしなければならないので、表示されているAPIトークンをコピーしてください。botの詳細、プロフィール画像そして名前を設定をしたら、Save Integrationをクリックします。
では、サンプルアプリを複製し、dependenciesをインストールしましょう。
git clone https://github.com/ggauravr/slack-sample-cli.git
cd slack-sample-cli
npm install
Structure Walkthroughプロジェクト
package.jsonのdependenciesリストの中で必要なものはslack-terminalizeのみですが、サンプルアプリには非同期式のコマンドの操作方法を示す例があります。requestモジュールはRESTコールに使われます。
config/
アプリに必要なJSONファイルはすべてここに表示できます。「できる」というのは、柔軟性に優れており、コンフィギュレーションパラメーター(これについてはあとで解説します)経由でさまざまなディレクトリと連動するように変更が可能だからです。アプリ構成のいろいろな方法のたった1つに過ぎませんが、Slack統合にまだ慣れていないなら、これに限定することをお勧めします。
commands.json
カスタムコマンドの追加を簡単にするのはこのコマンドです。各コマンドはキーと値のペアによって表示されています。キーはコマンド名(ここでは「初期名」と呼びます)で、値はコマンドで使用したいカスタムキーと値のペアを持つオブジェクトです。
ここでは、各コマンドに対し次のカスタムフィールドを使います。
- alias - コマンド用のエイリアス(別名、ここでは「二次名」と呼びます)で、Slackチャネル内でコマンドを呼び出すためにも使えます。初期名には短い名前を当て、別名にはもっと意味のある長い名前を使うのがベストです。
- description - コマンドの動作について簡潔で読みやすいように説明したものです。
- help - ヘルプメッセージ。man <command-name>またはhelp <command-name>のように動作します。
- exclude - このコマンドがユーザーが使用できるコマンド一覧として表示されたことを示すフラグです。開発目的、あるいはユーザーに見せる必要のないヘルパー(例:上にあるようなエラーコマンド)用のコマンドの場合があります。
- endpoint - タスクの実行を外部サービスに依存する場合、コマンドと応答するRESTエンドポイントです。
上に挙げたコマンドのうち、aliasは、初期名にユーザー入力したコマンドを調べられる唯一の鍵です。その他はオプションで省略もでき、目的に合えばコマンド内のどのようなフィールドの中でも、自由に使えます。
commands/
魔法のようなおもしろい作業ができるのはここからです。コマンドの動作を定義します。config/commands.jsonに指定された各コマンドは、初期名で使用されているキーと一致するファイル名を指定してマッチングさせる実装が必要です。ディスパッチャーが正しいハンドラーを起動させる方法です。少し融通が利かないところはあるものの、それでも使いやすく、またカスタマイズができます。
{
"help": {
"alias": [ "halp" ],
"endpoint": "#",
"help": "help [command](optional)",
"description": "To get help on all supported commands, or a specified command"
},
"gem": {
"alias": [],
"endpoint": "https://rubygems.org/api/v1/gems/{gem}.json",
"help": "gem [gem-name]",
"description": "Fetches details of the specified Ruby gem"
},
"error": {
"exclude": true
}
}
このファイルのキー名がcommands/ディレクトリ内のファイル名と同じになっていることに注意してください。
Code Walkthrough
index.js内のキーをbot用のものにSLACK_TOKENと置き換えます。CONFIG_DIRとCOMMAND_DIRはそれぞれを設定して、コマンドが実装されたことをslack-terminalizeに伝えます。
var slackTerminal = require('slack-terminalize');
slackTerminal.init('xoxb-your-token-here', {
// slack client options here
}, {
CONFIG_DIR: __dirname + '/config',
COMMAND_DIR: __dirname + '/commands'
});
次に、下のコマンドを使ってアプリを起動します。
node .
自分のSlackチームにWebサイトまたはアプリ上でログインします。botはデフォルトでチャネルに加えられていますが、Slack Command、/invite @<your-bot-name>を使えばプライベートなものを含むいかなるチャネルにもbotを招待できます。/invite @をタイプするとすぐにSlackがユーザーネームを提案してくれます。一覧にbotが含まれていなければ、戻ってbotを正しく統合したか確認してください。
helpかhalp(先ほど登場したエイリアスです)をチャネルでタイプすると、botがこちらの要求に動作するようになります。その動作を変えたりして commands/help.jsで遊んでみてください。実装時からわかるように、このコマンドはconfig/commands.jsonファイルからコマンド詳細をロードして動作しているだけです。つまり、同期性があるのです。時には、データベースを照会したり、応答を得るために、RESTエンドポイントを呼び出すように、非同期タスクの実行が必要な場合があります。その方法を次にご紹介します。
先にも書いたように、RESTコールをかけるためにrequestモジュールを使って、次のコードスニペット(Gemコマンド)がSlackでユーザーがタイプするGem名を検索します。commands/gem.jsを見てみると、メッセージが投稿された(closureを使って)チャネルが記憶され、動作が同じチャネル内に戻っているのが分かります。
var request = require('request'),
util = require('../util');
module.exports = function (param) {
var channel = param.channel,
endpoint = param.commandConfig.endpoint.replace('{gem}', param.args[0]);
request(endpoint, function (err, response, body) {
var info = [];
if (!err && response.statusCode === 200) {
body = JSON.parse(body);
info.push('Gem: ' + body.name + ' - ' + body.info);
info.push('Authors: ' + body.authors);
info.push('Project URI: ' + body.project_uri);
}
else {
info = ['No such gem found!'];
}
util.postMessage(channel, info.join('\n\n'));
});
};
Slackチャネルにgem abとタイピングすると、次のように表示されます。
先にも書きましたが、作業に慣れるためにもcommands/gem.js内の動作のフォーマットでしばらく遊んでださい。botが招待されたチャネルに動作し、要求をこなすようになります。次はカスタムコマンドの追加方法を説明します。
カスタムコマンドの追加実装
config/commands.jsonに新しいコマンドを追加します。先に説明した通り、キーは初期コマンド名になります。コマンドのエイリアスは「エイリアス」内の配列の値として、次のように表示されます。
{
"your-new-command": {
"alias": [ "command-alias", "another-alias", "yet-another-alias" ],
"help": "A short help message for the awesome new command",
"description": "Brief description of what the command does"
}
}
現在、スペースつきのコマンド名はサポートされていません。commands/ディレクトリに上のコマンドの初期名と同じ名前のファイルを作成してください(この場合、your-command-name.js)。module.exportsをコマンド実装機能に次のように割り当てます。
var util = require('../util');
module.exports = function (param) {
// param object contains the following keys:
// 1. command - the primary command name
// 2. args - an array of strings, which is user's message posted in the channel, separated by space
// 3. user - Slack client user id
// 4. channel - Slack client channel id
// 5. commandConfig - the json object for this command from config/commands.json
// implement your logic here..
// ..
// send back the response
// more on this method here: https://api.slack.com/methods/chat.postMessage
util.postMessage(param.channel, '<your-message-to-be-posted-back-in-the-channel>');
};
User、Channelオブジェクトについて詳しく知りたい場合には、node-slack-clientの資料を参照してください。
あとは新しいコマンドをプログラムし、アプリを再起動するだけです! これで新しいコマンドが動作するようになります。コマンド内にタイプして、思い通りに動くかどうか確認してください。
コンフィギュレーションで動作をカスタマイズ
slack-terminalizeモジュールにはオプションオブジェクトとコンフィグオブジェクトの2つのパラメーターが必要です。
var slackTerminal = require('slack-terminalize');
slackTerminal.init({
autoReconnect: true // or false, indicates if it should re-connect after error response from Slack
// other supported options can be seen here: https://github.com/slackhq/node-slack-client/blob/master/lib/clients/rtm/client.js
}, {
CONFIG_DIR: __dirname + '/config',
COMMAND_DIR: __dirname + '/commands',
ERROR_COMMAND: "error" // The filename it looks for in COMMAND_DIR, in case the user entered command is invalid
})
さらに詳しいパラメーターについては、こちらの資料をチェックしてください。
さらにすべきことは?
- チーム用のコマンドを定義します。楽しく作業して、生産性を高めましょう。
- slack-terminalizeプロジェクトとサンプルアプリを分けます。楽しくいじりながら改善していきましょう。
参考リンク
- Slack Bot Users
- Slack APIs
- Hubot
- Hubot Slack
- Node Slack Client
- Introduction to WebSockets
- REST vs WebSockets
- How to create and publish your first Node module
- Checklist for your new Open Source JS project
(原文:Build Your Own Custom SlackBot with Node.js)
[翻訳:加藤由佳]
[編集:Livit]