JavaScript
Node.js
Slack
slackbot
slack-api

Slack API 新機能の Block Kit を使ってより情報的なレストラン検索コマンドを作ろう

title.png

こんにちは、みなさんいかがお過ごしですか。さて最近、Slack の新しいメッセージ UI フレームワーク Block Kit がリリースされました。これを使えば皆さんの Slack アプリから送信できるメッセージがさらにリッチになり、例えばタスクのリストや投票結果などが一つのメッセージでより情報的になります。

Block Kit は、ビジュアルツール Block Kit Builder を使うとさらに簡単にプロトタイプでき、さらにこのツールで生成された JSON ストリングをコードにそのまま組み込むことができます。

このチュートリアルでは、シンプルなレストラン検索結果を表示させ、そのユーザ・エクスペリエンスを Block Kit で改良してみましょう。


はじめるのに必要なもの

このチュートリアルでは Node.js を使って Slash Command アプリを作成していきます。JavaScript または他のプログラミング言語の経験が必要です。

コードは、ブラウザ上でコードを実行できデプロイの必要もない、ウェブ IDE である Glitch を使用します。

まずは下のリンクから “remix” して始めましょう。リミックスとは GitHub でいう Fork のようなものだと思ってください。

ソースコード: https://glitch.com/edit/#!/slash-blockkit

Remix のリンクはこちら: https://glitch.com/edit/#!/remix/slash-blockkit

それから、ここではこのアプリをもっとおもしろくするために Yelp API を使用していますので、このサイトから Yelp Client ID と API key を取得してください。詳しくは Yelp ディベロッパー・サイトから。

では始めましょう。このアプリ上でのユーザーの手順は次の通りです。


  1. まずユーザが、/find-food [食のジャンル], [場所] (例 /find-food burrito, San Francisco) とコマンドを入力

  2. アプリが検索結果のレストランを表示


Slash Command に応答

まずは /find-food コマンドに応答してごくシンプルなテキストを返す、という Slash command から作っていきましょう。


アプリのセットアップ

まずは Slack App Config page で新規アプリの作成をします。

次に slash command を加え、そのコマンド情報を入力します。ここでは /find-food としてみましょう。使われるパラメータは、食べ物と場所をコンマで区切った短い文になります。例えば /find-food pancake, shibuya のようになります。

Request URL は https://your-server/route となりますので Glitch から “remix” した際は Glitch がランダムな単語ふたつからなるプロジェクトネームを生成しますので、Request URL は https://sassy-shrimp.glitch.me/command のような感じになります。

config_edit_command.png

次に Settings > Basic information で Signing Secret key を取得します。

このキー情報と、Yelp API の認証情報は、🗝 .env ファイルに保管します。

SLACK_SIGNING_SECRET=fca39e3de...

YELP_CLIENT_ID=sIskJpLm5f...
YELP_API_KEY=ySz84qKNl...

(Glitch 上のサンプルコードには .env.sample というファイルを用意してあるので、それを .envにコピーし、認証情報を入力してください。)

下の Node.js コードでは依存するモジュールをインポートし、 Express サーバを動かしています。それからここでは、リクエスト署名(Signing Secret)を検証するために、リクエスト・ペイロードをそのまま受け取っています。(詳しくはこの日本語記事と、チュートリアルから。)

const express = require('express');

const bodyParser = require('body-parser');
const axios = require('axios');
const signature = require('./verifySignature');

const app = express();

const rawBodyBuffer = (req, res, buf, encoding) => {
if (buf && buf.length) req.rawBody = buf.toString(encoding || 'utf8');
};

app.use(bodyParser.urlencoded({verify: rawBodyBuffer, extended: true }));
app.use(bodyParser.json({ verify: rawBodyBuffer }));

const server = app.listen(process.env.PORT || 5000);


シンプルなメッセージの送信

次に HTTP POST メソッドルートを使って Slash command のリクエスト・ペイロードを受け取るためのエンドポイントを作ります。受け取りの時にそのリクエストが正規に Slack からきているものかを確認します。認証できた場合にのみ、ユーザからのパラメータを受け取りパースします。この場合、食べ物ジャンルと場所のクエリを受け取ります。

そのクエリを Yelp API に投げ、その結果を受け取ります。

app.post('/command', async (req, res) => {

if(!signature.isVerified(req)) {
res.sendStatus(404);
return;

} else {
const query = req.body.text ? req.body.text : 'lunch, San Francisco';
const queries = query.split(',');
const term = queries.shift(); // "Pizza"
const location = queries; // "San Francisco, CA"
const places = await getPlaces(query, location);
}

const message = { // will generate it }
res.json(message);
}

このコードでは getPlaces() 関数で Yelp API から結果をうけとる仕組みにしています。 (詳しくはソースコードを見てください。)

Yelp REST API から返された結果の JSON 配列は次のような感じになります。(チュートリアルをわかりやすくするために、 JSON の内容を簡略化しています)

[{ 

name: 'Zero Zero',
image_url:
'https://s3-media2.fl.yelpcdn.com/bphoto/JB5XNOgdQHocE4nI9DHWkA/o.jpg',
url:'https://www.yelp.com/biz/zero-zero-san-francisco',
review_count: 3128,
rating: 4,
price: '$$',
location:
{
display_address: [ '826 Folsom St', 'San Francisco, CA 94107' ] },
phone: '+14153488800'
}...
]

まず、この情報の一部を使って、コマンドを送信したユーザーにごく簡単なメッセージを返してみましょう。サーバには HTTP 200 のレスポンスをすれば良いのですが、この場合は JSON を返しましょう。Slack チャンネル上に全ユーザーに見えるようにメッセージする場合は下のような JSON を送信します。

const message = {

response_type: 'in_channel',
text: places[0].name,
};
res.json(message);

このレスポンスは Slack 上ではこう表示されます。

slack_message_simple.png


Block Kit でメッセージにもっと情報を

さて、次は Block Kit エレメントで、このメッセージを、もっと情報の入った読みやすいメッセージにしましょう。

Block Kit は、組み立て可能のレイアウトやエレメントといったブロックで構成されています。

今からこれを使って、メッセージレイアウトを構成する JSON に書き換え、リッチなメッセージでレストラン情報を表示させましょう。

blockkit_message.gif

ビジュアル・サンドボックスである Block Kit Builder を使ってブラウザ上でデザインをプロトタイプしていきましょう。

左側にあるメニューからレイアウトもしくはエレメントを選んでいくと、真ん中にはプレビューが、右には生成された JSON 配列が表示されます。

blockkit_builder.gif

生成されたコードをコピーして、前のコードの message オブジェクトの text: places[0].name 部分を下のように block として書き換えます。


const message = {
response_type: 'in_channel',
blocks: [
// Result 1
{
type: 'section',
text: {
type: 'mrkdwn',
text: `*<${places[0].url}|${places[0].name}>* \n${places[0].location.display_address.join(' ')} \n\nRating: ${places[0].rating} \nPrice: ${places[0].price}`
},
accessory: {
type: 'image',
image_url: `${places[0].image_url}`,
alt_text: 'venue image'
}
},
{
'type': 'context',
'elements': [
{
'type': 'plain_text',
'text': `${places[0].review_count} Yelp reviews`,
'emoji': true
}
]
},
{
'type': 'divider'
},
}
]
};

エレメントで text type 部分を mrkdwn とすることによってテキストに太字やリンクなどのマークダウンをしようすることもできます。詳しくは An overview of message composition(英語)から。

では、できあがった Slash command をクライアント上で試してみましょう!こんな感じに表示されましたか?🙆

slack_message_blockkit.png

いぇ〜い! でもなんだか、お腹すいてきたわ。


他にはどうやってリッチ・メッセージが送信できる?

さて、今回は Slash command の結果をリッチ・メッセージで送信してみましたが、他にもいろいろな機能を一緒に使えます。例えば incoming webhooks、WebAPI の chat.postMessagechat.postEphemeral メソッドで送信できます。アクションなどのインタラクティブ機能にも使えことができます。


ベスト・プラクティス

Block Kit Builder 上でメッセージレイアウトやエレメントをいろいろ試してみてクリエイティブなメッセージ UI を試してみてください。プロトタイプで遊んでみるのは大いに推奨しますが、ここで気をつけてもらいたいのは、実際のプロダクトとなる UI を構築するにはまず何よりもユーザーを第一に考えてください。メッセージは読みやすいものでなくてはなりません。可能だからと言って何もかも詰め込むのはよくありません。内容が多くなってしまう場合には、たとえば “Read more…” など、さらなるユーザーアクションを促すボタン機能などを追加するのもいいかもしれません。

いろいろプロトタイプを作ってテストしてみましょう。モバイルでのテストも忘れないでください。


このチュートリアルによって、なにか新しいアイデアが浮かんだり、前に作ったアプリの改良などをしてみたい、と思ってくれていたら幸いです!


📄 Related Slack API Documentation

An overview of message composition - Slack Platform documentation

Block Kit Builder - UI Tool

Slack Commands - Slack Platform documentation

2019年3月現在では、Slack API ドキュメントは全て英語のみですが、すこしづつ日本語化でしていけたら、と思っています。リクエストなどがありましたら遠慮せず、開発者サポート (developers@slack.com) にメールするか、@SlackAPI にツイートしてみてください。


英語でこの記事を読みたい方はこちらもどうぞ。

Building More Contextual Restaurant Search Slash Command Results with Block Kit by Tomomi Imura