ForSchool事業部開発グループの松田です。
弊社ではSlackを導入しており、いわゆるChatOpsに活用しています。
Slackにはさまざまな便利機能がついていますが、その中の一つにSlash Commandsというものがあります。
今回はこのSlash Commandsを自作して、そのバックエンドにFirebaseを利用した例をご紹介します。
Slack Slash Commandsとは
Slack内で /command
のようにスラッシュから始まるコマンドを発言することで、特定のエンドポイントにリクエストを送信する機能です。
実装次第では /command sugoi argument
などのように引数をとることもできるので汎用性が高いです。
標準機能として /remind
や /invite
など複数のコマンドが定義されています。
Built-in slash commands – Slack Help Center
Firebaseとは
ここのところ流行っている感の強いモバイルプラットフォームです。
Googleの展開しているプラットフォームで、最近では国内の導入事例も見かけるようになりました。
認証周りをやってくれるAuthenticationや、ファイルホスティングのCloud Storageなどいくつかのサービスがありますが、
今回はJSの関数をさまざまなトリガーで実行できるCloud Functionsと、DBを提供するFirestoreを利用して簡単なコマンドを実装してみました。
実際に作ってみる
今回は弊社のボードゲーム部で使用する /boardgame
コマンドを実装してみます。
仕様は /boardgame search players 4
の入力からDBを検索しプレイ人数に合致するボードゲームを返すコマンドとします。
Slash Commandsはコマンドが発行されると設定したURLにリクエストを投げるので、その先をCloud Functionsで受ける感じでやっていきます。
Slack AppとFirebaseプロジェクトを新規登録
Slack AppとFirebaseのプロジェクトを新規に登録します。
名前などいい感じにしましょう。
Cloud Functionsを仮デプロイ
firebase/firebase-toolsを使って、ローカルにFirebaseのプロジェクトと連携するフォルダを作ります。
初回設定時に使用するサービスを選択しますが、前述の通りFunctionsとFirestoreを選択します。
Functionsを選択するとJSとTSのどちらで書くかを聞かれますが、なんとなくTSにしました。
この時点で ./functions/src/index.ts
に helloWorld
というHTTPリクエストがトリガーの関数が定義されているので、 boardgame
に改名して、以下のコマンドを実行します。
$ firebase deploy --only functions
すると、デプロイ結果としてトリガーになるURLが表示されるので、ブラウザ等でリクエストをして Hello, World!
と表示されればここまでは完了です。
自作Appをワークスペースに追加する
先程作ったSlack Appの設定メニュー「Slash Commands」からコマンドを登録します。
Request URLには先ほどデプロイしたCloud Functionsに割り当てられたURLを設定しておきます。
あとはよしなに設定します。
Slash Commandから呼ばれる関数を実装する
とりあえず手元にボードゲームのリストを用意しました。
{ "title": { "ja": "アグリコラ", "default": "Agricola" }, "minPlayers": 1, "maxPlayers": 5, "minPlayingTime": 30, "maxPlayingTime": 150, "properAge": 12 }, { "title": { "ja": "デッドオブウィンター", "default": "Dead of Winter: A Crossroads Game" }, "minPlayers": 2, "maxPlayers": 5, "minPlayingTime": 60, "maxPlayingTime": 120, "properAge": 13 }, ...
これを適当なスクリプトを書いてFirestoreに突っ込みました。
最近使えるようになった配列の array-contains
オペレータを使ってみたかったので、
Firestoreのトリガーで、minPlayersとmaxPlayersを下限と上限にした数値の配列をplayersフィールドに追加するようにしています。
そして実装ですが、公式のチュートリアルを参考にざっくりと以下のようになりました。1
import * as functions from 'firebase-functions'; import * as admin from 'firebase-admin' admin.initializeApp() const db = admin.firestore() class RequestError extends Error { code: number; } const commands = ['search']; const searchFunction = (args) => { return new Promise((resolve, reject) => { }) } export const boardgame = functions.https.onRequest((req, res) => { return Promise.resolve() .then(() => { if (req.method !== 'POST') { const error = new RequestError('Only POST requests are accepted'); error.code = 405; throw error; } if (!req.body || req.body.token !== functions.config().slack.token) { console.log(req.body, functions.config().slack.token); const error = new RequestError('Invalid credentials'); error.code = 401; throw error; } const [command, ...args] = req.body.text.split(' '); if (command == 'search') { let query:FirebaseFirestore.Query | FirebaseFirestore.CollectionReference = db.collection('games') if (args.includes('players')) { const playersIndex = args.indexOf('players') + 1; const players = args[playersIndex]; query = query.where('players', 'array-contains', parseInt(players)); } query.limit(3).get().then((querySnapshot) => { resolve({ text: 'Boardgame Search Result', attachments: querySnapshot.docs.map((doc) => { const game = doc.data(); return { title: game.title.ja, text: game.title.default, fields: [ { title: 'Players', value: `${game.minPlayers} ~ ${game.maxPlayers}`, short: true, }, { title: 'Time', value: `${game.minPlayingTime} ~ ${game.maxPlayingTime}`, short: true, }, { title: 'Age', value: game.properAge, short: true, } ] } }), }); }) } else { return new Promise((resolve, reject) => { resolve({ text: 'Unknown command!', attachments: [], }) }) } }) });
functions.config().slack.token
にはSlack App Basic Information内のVerification Tokenを予め設定しておきます。
Cloud Functionsにデプロイする
再度を以下のコマンドを実行してCloud Functionsにデプロイします。
$ firebase deploy --only functions:boardgame
Slach Commandを実行する
Slack Appを追加したワークスペースのチャンネル上で以下を発言します。
/boardgame search players 1
便利ですね。
まとめ
Firebaseは運用コストが低いので、こういったピンポイントなAppのバックエンドとしては最適だと思いました。
FirebaseのいろいろなサービスもCloud Functionsからスッと使えるので対応できる幅も広そうです。
一方で、今回使用したCloud FunctionsとFirestoreは(2018/08/20現在)ベータでの提供なので留意してください!
また、弊社では就業後にボードゲーム部が不定期活動中です!
ボードゲームがしたい、Firebaseで業務改善したいエンジニアをどしどし募集しております〜。
まずはお気軽にオフィスに(ボードゲームを)遊びに来てください〜。
採用情報 | スタディプラス株式会社
-
このチュートリアルはFirebaseではなくGoogle Cloudのものなので、設定値の取得方法など微妙に差異があるので注意してください。↩