皆さん、スキル内課金対応スキル作っていますか?スキルチャレンジもあるし、Echo Show5がもらえる公式キャンペーンもあるし、で、やるしかないですよねー。ということで、現在スキル内課金対応スキルを作成している今日このごろです。
で、話は変わって、Advent Calendar延長戦として、今回はVoiceflowでFirebaseと連携させてみようと思います。
Firebaseとは?
最初にFirebaseについてです。このあたりを見ればよいのではないかと思います。
簡単にまとめると、
- Google提供の、モバイルおよび Web アプリケーションのバックエンドサービス
- 以下を提供してくれる
- データベース
- 認証機能
- ストレージ
- メッセージング
- サーバレスの実行環境
- これらを活用することで、アプリケーションの開発に専念できる
というものですね。とても便利そうです。
なぜVoiceflowでFirebase?
メリットは上に書いたとおりですが、なぜVoiceflowで使うのか?ということです。今回は特にデータベースの部分に注目してみたいと思います。
Voiceflowでデータの管理を行う方法は、標準だと以下になるかと思います。
どちらも気軽に使えるので便利なのですが、それぞれデメリットもあります。
- Project Variablesを使って永続データとして管理
- 変数はあくまでもスキル内からしか利用できない。スキル開発者が管理することができない。
- 集計などの横断的なアクセスができない。
- Googleスプレッドシートをデータベース的に使う
- アクセスが遅く、エラーが出たりなど、イマイチ信頼性が低い
- Integration Blockの設定が面倒かつあまり融通が利かない
そこで、Firebaseの出番です。今回はVoiceflowのフォーラムにある、以下のチュートリアルを参考に、ユーザデータの管理をFirebase Realtime Databaseで試してみたいと思います。
管理するデータ
ユーザIDは、Voiceflowのuser_id変数で取得できますので、これをキーに以下の情報を紐付けて、Firebaseに保存するようにしたいと思います。
プロジェクトの作成
https://firebase.google.com/ にアクセスして、右上の「コンソールへ移動」をクリックします。

「プロジェクトを追加」をクリックして、新しいプロジェクトを作成します。

プロジェクト名を入力します。ここでは"firebase-voiceflow-sample"としました。「続行」をクリックします。

プロジェクトの解析にGoogleアナリティクスを使用するか?を聞かれますが、今回は不要とします。「このプロジェクトで Google アナリティクスを有効にする」をオフにして、「プロジェクトを作成」をクリックします。

プロジェクトが作成されます。完了するまで待ちます。

完了したら、「続行」をクリックします。

以下の画面が表示されればプロジェクトの作成は完了です!

プロジェクトの設定(ユーザの作成)
では、Voiceflowから使えるようにプロジェクトの設定していきます。
まず、ユーザを作成します。ここでいうユーザは、Alexaスキルのユーザではなく、Firebaseへのアクセスを行うための、いわば特権ユーザになりますので注意してください。左のメニューの"Authentication"をクリックします。

Authenticationのページが開いたら、「ログイン方法を設定」をクリックします。

ログイン プロバイダの設定で、「メール/パスワード」にカーソルを合わせると、右に鉛筆アイコンが表示されますので、クリックします。

メールアドレス・パスワードでのログインを「有効」にして「保存」をクリックします。

メールアドレス・パスワードでのログインを「有効」になったことを確認したら、「ユーザ」をクリックします。

ユーザ画面が表示されたら、「ユーザを追加」をクリックします。

メールアドレスとパスワードを入力する画面が表示されます。なお、ここで入力するメールアドレスとパスワードは、あくまでもFirebaseへのアクセスの認証に使用するものなので、実際に存在するメールアドレスでなくてもかまいません。あとでVoiceflowからの接続時に必要になりますのでどこかに控えておいてください。入力したら「ユーザを追加」をクリックします。

メールアドレスが登録されたことを確認できればOKです。このとき、ユーザUUIDというランダムな文字列が発行されます。

UUIDにマウスカーソルを合わせると、コピーするためのアイコンが出てきます。これをクリックするとクリップボードにコピーされますので、これもどこかに控えておいてください。

これでユーザの作成は完了です。
プロジェクトの設定(Realtime Databaseの設定)
では、データベースを作成していきます。Firebaseでは2種類のデータベースが利用可能です。
- Cloud Firestore
- Realtime Database
Cloud Firestoreのほうが新しいのですが、今回はRealtime Databaseを使用します。
左のメニューから、"Database"をクリックします。

データベースの設定画面が開きます。すぐに「データベースの作成」が表示されていますが、これはCloud Firestoreの方なので注意してください。Realtime Databaseの方は、下にスクロールしたところにあります。

ここですね、こちらの方の「データベースを作成」をクリックします。

データベースのセキュリティルールの設定画面が出てきます。このルールは後で設定しますので、「ロックモードで開始」が選択されていることを確認の上、「有効にする」をクリックします。

データベースが作成され、データベースの設定画面が表示されます。ここでルールの設定を行います。上のメニューから「ルール」をクリックします。

ルール画面が開いたら、以下のように表示されているかと思います。ここに先程のUUIDを設定します。

以下のように入力してください。ユーザUUIDの部分は先ほど作成したUUIDで置き換えてください。ダブルクォートとシングルクォートが見にくいので気をつけてください。
{
"rules": {
".write": "'ユーザUUID' === auth.uid",
".read": "'ユーザUUID' === auth.uid"
}
}
入力したら「公開」をクリックします。

「ルールを公開しました」と表示されればOKです。

最後に、データベースにアクセスするためのAPIエンドポイントURLとAPIキーを取得します。まず、APIエンドポイントURLは上のメニューから「データ」をクリックします。

表示されているURLがAPIエンドポイントURLになりますので控えておいてください(ダブルクリックすると選択できます)。

次にWEB APIキーです。左上の歯車アイコンをクリックして、「プロジェクトの設定」をクリックします。

全般タブに「WEB APIキー」が表示されるので、これも控えておいてください。

はい、これでFirebase側の準備は完了です。
Voiceflowでスキル作成
いよいよVoiceflow側でスキルを作成していきます。あくまでもサンプルなので、単にスキルを起動・終了したら各種情報を保存するだけになっています。まず全体図を見てください。(ちなみに、VoiceflowのCanvasが新しくなりました。見た目が少し落ち着いた感じになり、高速かつスムーズな操作が可能になってます。一部UIも変わっていますので、それにあわせて説明しています。)
まず、メインフローです。一番最後にFlow Blockをおいて、Firebaseへのアクセスはその中でやっています。

Firebaseへのアクセスを行うFlow Blockの中身はこんな感じです。

ではそれぞれのフローを見ていきましょう。
メインフロー

まず、以下のプロジェクト変数を作ります。

それぞれの変数の意味は以下です。Voiceflow TIPS #1 Alexaの所在地情報を使う - kun432's blog で、Echoデバイスの所在地情報を取得した際とやってることはほぼ一緒です。今回は、所在地情報ではなく、Echoデバイスのタイムゾーンを取得して日本における日時等を取得するために「Alexa設定API」を使います。
| 変数名 | 説明 |
|---|---|
| deviceId | デバイスID。Alexa設定APIへアクセスするために必要。デバイスからのリクエストに含まれている。 |
| apiAccessToken | APIアクセストークン。Alexa設定APIへアクセスするために必要。デバイスからのリクエストに含まれている。 |
| apiEndpoint | APIエンドポイント。AAlexa設定APIへアクセスするために必要。デバイスからのリクエストに含まれている。 |
| hasDisplay | ディスプレイ対応有無 |
| startTime | スキル起動時の日時 |
今回これらをプロジェクト変数にしたのはFlow Blockから参照するためです。Voiceflowにおける変数のスコープについては、以前の記事を参考にしてください。
では各ブロックを見ていきます。
①タイムゾーン情報取得に必要なデバイス情報や、スキル実行開始日時をCode Blockで取得
Code Blockで、デバイスのタイムゾーンを取得するのに必要なデバイス情報や、スキル実行開始時間を取得しておきます。

コードはこんな感じです。
// デバイス情報取得
deviceId = _system.device.deviceId;
apiAccessToken = _system.apiAccessToken;
apiEndpoint = _system.apiEndpoint;
hasDisplay = voiceflow.capabilities && ('Alexa.Presentation.APL' in voiceflow.capabilities);
// 時刻情報取得
startTime = new Date();
このあたりは以前にご紹介した以下の記事でも紹介していますので、よろしければ参考にしてください。
② スキル起動時のメッセージを発話するSpeak Block
スキル起動時のメッセージを発話させます。最後の「続けますか?」というのを受けて、次のInteraction でユーザの発話を取ります。

③スキルをすすめるかどうかを確認するInteraction Block
前のSpeak Blockを受けて、「はい」と発話されたら次に進みます。それ以外の発話の場合は⑤でもう一度Interactionに戻して「はい」が発話させるまでループさせます。

⑤「はい」の発話を促すSpeak Block
順番前後しますが、③のInteractionで「はい」以外が発話された場合に、「はい」を言うように促します。ここで「はい」以外を繰り返すことで、スキル実行時間が変化することを確認するためです。

④ Firebaseアクセス処理を行うフローへのFlow Block
今回のテーマのメインであるFirebaseへのアクセスは、Flow Blockから"firebase flow"というフローを作ってそちらで処理を行うようにしています。"Enter Flow"で中身を見てましょう。

Firebaseにアクセスするfirebase flow
ここが今日のポイントですね。

まず最初にフロー変数を作ります。これらの変数はフローの中でしか使わないし、永続化する必要もないので、フロー変数にしています。

それぞれの変数の意味は以下です。
| 変数名 | 説明 |
|---|---|
| firebase_url | Firebase Realtime DatabaseのAPIエンドポイントURL(データベース作成時に取得したもの) |
| firebase_user | Firebase Realtime Databaseへアクセスするためのメールアドレス(データベース作成時に取得したもの) |
| firebase_passwd | Firebase Realtime Databaseへアクセスするためのパスワード(データベース作成時に取得したもの) |
| firebase_key | Firebase Realtime DatabaseへアクセスするためのWEB APIキー(データベース作成時に取得したもの) |
| user_amazonId | デバイスから送られてきたユーザID |
| user_timezone | Alexa設定APIから取得したユーザのタイムゾーン |
| user_sessionTime | スキルの実行時間 |
| user_startDate | スキルの実行開始日 |
| user_startTime | スキルの実行開始時刻 |
| user_endDate | スキルの実行終了日 |
| user_endTime | スキルの実行終了時刻 |
| firebase_idToken | Firebaseへの認証後に返されるアクセストークン(これを使ってデータベースへの読み書きを行う) |
では順にフローを見ていきます。
① Firebaseへのアクセス情報を設定しておくSet Block
Firebaseへのアクセスに必要な情報を変数にセットするためにSet Blockを使います。ここを間違えるとデータベースにアクセスができないので注意してください。

| 変数名 | 設定内容 |
|---|---|
| firebase_url | Firebase Realtime DatabaseのAPIエンドポイントURLを指定 |
| firebase_user | Firebaseで作成したユーザのパスワードを指定 |
| firebase_passwd | Firebaseで作成したユーザのパスワードを指定 |
| firebase_key | WEB APIキーを指定 |
②Alexa設定APIへのアクセスを行うIntegration Block & エラー時のメッセージを行うSpeak Block
まず、最初にAlexa設定APIへアクセスして、デバイスのタイムゾーンを取得します。このあと出てくるCode Blockの中でJavaScriptを使って日時を取得するのですが、何も考えずに時刻を取得しようとするとJavaScriptが動いている実行環境、つまり、Voiceflow側のタイムゾーン、アメリカになってしまいます。そこでデバイスが動作しているタイムゾーンをAlexa設定APIを使って取得した上で、それを踏まえてJavaScriptで日時を取得すれば、ユーザが利用している日時になるというわけです。
ということで、Alexa設定APIにアクセスするために、Integration BlockでCustom APIを使います。Custom APIタイプの設定をExpandしたメニューで説明しますね(Integration Blockの設定メニューの右上の歯車アイコンをクリックして、Expandを選択してください)。

それぞれの設定はこんな感じです。

- リクエストURLは、種類は”GET”で、URLに ”
{apiEndpoint}/v2/devices/{deviceId}/settings/System.timeZone” を指定します。 - リクエストパラメータは、"Headers"タブに以下を設定します。
- "Enter HTTP Headers" に "Authorization" を入力
- "Value" に "Bearer
{apiAccessToken}" を入力
- 結果を変数にマッピングします。"Transform into Variables"に以下を設定します。
- "Enter object path" に "response"を入力
- "APPLY TO" で 変数"user_timeZone" を選択
で、Test Integrationからテストしたいところですが、残念ながらこのAPIはEchoデバイス経由でないと動作しないので、ここではテストを割愛します。
最後にfailからSpeak Blockをつなげて、APIリクエストが失敗した場合のエラーメッセージを発話させるようにします。一旦はこのままにしておいて動作確認が取れたら最後にちょっとだけいじります。

③ タイムゾーンを使って日時取得を行うCode Block
②で取得したタイムゾーン情報を踏まえて、JavaScriptで日時情報やスキルの実行時間を取得します。

コードはこんな感じです。
// AmazonユーザID
user_amazonId = user_id.split(".");
user_amazonId = user_amazonId[3];
// 開始・現在・経過をDateオブジェクトで取得
var now = new Date();
var start = new Date(startTime);
var duration = Math.abs(now.getTime() - start.getTime())/1000;
// 経過時間取得
var hours = Math.floor(duration / 3600) % 24;
var minutes = Math.floor(duration / 60) % 60;
var seconds = duration % 60;
user_sessionTime = hours+":"+minutes+":"+Math.round(seconds);
// 日時・時刻取得
user_startTime = new Date(startTime).toLocaleTimeString(locale, { hour: '2-digit', minute: '2-digit', second: '2-digit', timeZone: user_timeZone });
user_startDate = new Date(startTime).toLocaleDateString(locale, { year: 'numeric', month: '2-digit', day: '2-digit', timeZone: user_timeZone });
user_endTime = new Date().toLocaleTimeString(locale, { hour: '2-digit', minute: '2-digit', second: '2-digit', timeZone: user_timeZone });
user_endDate = new Date().toLocaleDateString(locale, { year: 'numeric', month: '2-digit', day: '2-digit', timeZone: user_timeZone });
少しだけ解説しておきます。
- 1〜3行目でEchoデバイスから取得したAlexaのユーザIDを少しだけいじります。ユーザIDは
amzn1.ask.account.XXXXXXX・・・となっており、amzn1.ask.accountは共通となるため、取り除いている感じです。 - 5〜14行目で、メインフローの最初で取得したスキルの実行開始日時と現在の日時を比較して、スキルの実行時間を計算してます。
- 16行目以降で、スキル実行開始日時と終了日時をそれぞれ文字列として使えるように整形しています。
エラー時の処理はintegration Blockと同じような感じです。

④ Firebaseへアクセスするための認証を行うIntegration Block
いよいよFirebaseにアクセスしていきます・・・といいたいところですが、Firebaseへのアクセスは、最初に認証を行い、認証成功後に受け取るトークンを使ってデータベースへの読み書きを行う形となります。まずはFirebaseへの認証処理をIntegration Blockで設定していきます。

順に設定をExpandメニューで見ていきます。


- リクエストURLは、種類は”POST”で、URLに ”https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyPassword?key=
{firebase_key}” を指定します。 - リクエストパラメータは、"Headers"タブに以下を設定します。
- "Body"タブをクリックして、以下を設定します。
- タイプは"Raw"を設定して、以下を入力します。
{
email: "{firebase_user}",
password: "{firebase_passwd}",
returnSecureToken: true
}
- 結果を変数にマッピングします。"Transform into Variables"に以下を設定します。
- "Enter object path" に "response.idToken"を入力
- "APPLY TO" で 変数"firebase_idToken" を選択
BodyタイプでRawを指定した場合、Bodyに変数が含まれていても、Test Integration では指定できないので、ここもテストはできません。割愛します。
エラー時の処理はこれまでと同じような感じです。

⑤ Firebaseへ書き込みを行うためにIntegration Block
④で取得したトークンを使ってデータベースに書き込みを行います。Integration BlockのCustom APIを使います。

設定はこんな感じです。


- リクエストURLは、種類は”PATCH”で、URLに ”
{firebase_url}/users/{user_amazonId}.json” を指定します。 - リクエストパラメータは、"Headers"タブに以下を設定します。
- "Body"タブをクリックして、以下を設定します。
- タイプは"Raw"を設定して、以下を入力します。
{
"userID": "{user_id}",
"timestamp": {timestamp},
"datetime_start": "{user_startDate} {user_startTime}",
"datetime_end": "{user_endDate} {user_endTime}",
"session_time": "{user_sessionTime}",
"hasDisplay": {hasDisplay},
"sessions": {sessions},
"userTimezone":"{user_timeZone}"
}
結果を変数で取得する必要はありません。ここもテストはできませんので割愛します。エラー時の処理も同じような感じで設定してください。

⑥ スキル終了の最後のメッセージを発話するSpeak Block
エラーが発生しなければ、ここにきますので、スキルの終了を発話させます。

はい、これで終了です
テスト
では開発者コンソールから早速テストしてみましょう。

サンプルなのでこれだけです。「登録しました」と出ているので、一通りの処理は成功しているようですね。Firebaseの方にも登録されているか見てみましょう。

ユーザ情報が登録されました。初めてのセッション、起動時間が6秒間、ディスプレイなし、タイムゾーンが日本、になっていますね。
もう一度テストしてから見てみるとこうなります。

セッションが2回めになっていて、今回は起動時間が変わっているのがわかりますでしょうか。ちなみに、hasDisplayはDisplay Blockを置かないと値が取れません。今回のサンプルではDisplay Blockを置いてないのでこの値は変わりませんので、実際にディスプレイ対応スキルを作る際の参考だと思ってください。
おまけ
Firebase Flowの中で、各処理ごとにSpeak Blockでエラー内容を発話させていましたが、動作が確認できればこの処理は消しちゃっても良いと思います。今回の例では、あくまでもユーザ情報を取得するのが目的なので、エラーをいちいちユーザに伝える必要はないということで。取れない場合はしょうがないという割り切りですね。もちろん使い方によっては、きちんとエラー処理を行わないといけない場合もあると思いますので、ケースバイケースで判断してください。
今回はこんな感じでまるっと終了させてみました。

まとめ
今回始めてFirebase使ってみたのですが、認証やAPIが予め用意されていてさらっと外部のデータベースを使いたい、という場合にとても良いですね。Voiceflowのプロジェクト変数やGoogleスプレッドシートを使っての管理もお手軽でいいのですが、こちらのほうがより高速で安定していて管理もしやすそうなので、いろいろ活用の幅が広がりそうです。ぜひトライしてみてください。
最後に、今回の参考にさせていただいた素晴らしいチュートリアルの作者のNicolas Arcay Bermejo(@ArcayBermejo)さんに感謝! Nicolasさんはこれ以外にも多数の素晴らしいチュートリアル記事を書かれていたり、Community Marketplaceにも多数デモスキルを提供されていますので、ぜひぜひ見てみてください!