actions SDK Carousel のサンプルコード
Actions SDK を使っている時にカルーセルの使い方がわからなかったので色々調べていた
結論としては使い方とか全部ライブラリのコードにコメントアウトで書いてあったのだけど
いつかそのことを忘れてググってもすぐでてくるようにメモ
const app = new ApiAiApp({request, response}); const WELCOME_INTENT = 'input.welcome'; const OPTION_INTENT = 'option.select'; function welcomeIntent (app) { app.askWithCarousel('Which of these looks good?', app.buildCarousel() .addItems([ app.buildOptionItem(SELECTION_KEY_ONE, ['synonym of KEY_ONE 1', 'synonym of KEY_ONE 2']) .setTitle('Number one'), app.buildOptionItem(SELECTION_KEY_TWO, ['synonym of KEY_TWO 1', 'synonym of KEY_TWO 2']) .setTitle('Number two'), ])); } function optionIntent (app) { if (app.getSelectedOption() === SELECTION_KEY_ONE) { app.tell('Number one is a great choice!'); } else { app.tell('Number two is a great choice!'); } } const actionMap = new Map(); actionMap.set(WELCOME_INTENT, welcomeIntent); actionMap.set(OPTION_INTENT, optionIntent); app.handleRequest(actionMap);
actions SDK GoogleHome と スマホ で処理を分けたくなった時に使うTIPS
GoogleAssistantアプリを作るチュートリアル的なものを作りたかった
概要
GoogleAssistantアプリの開発プラットフォームがついに日本語にも対応しましたね
せっかくなので簡単なアプリをリリースしました
そしてその作ったアプリの過程をまとめて、GoogleAssistantアプリを作るチュートリアルを作りました。。。。
と言いたかったのですがチュートリアルとしては微妙なものができてしまいました
しっかりした解説はないけど、GoogleAssistantアプリを作ったことないけど作りたい人には役立つと思うのでよかったら読んでください
Projectの作成
Actions on Google の Console に行って add Project をします
https://console.actions.google.com/
するとProjectNameとCountryを選ぶ箇所があるので選んでCreateProjectを押してください
するとこんな感じで API.AI を使うか Actions SDK を使うかみたいに選ぶ画面になるので選んでください 今回はAPI.AIを使って行くのでAPI.AIを選んでください
ここで選んだプラットフォームは取り消せないので、間違えて選んでしまった人は新しくプロジェクトを作り直してください
API.AIを選ぶとこんな感じでAPI.AIのプロジェクトを作れよと言われるので作ります
API.AIの画面に行くとdescription, language, timezone などを設定する場所があります。Alarmとかみたいなものを選択できる箇所もあるのですが、日本語は対応していないので、languageで日本語を選択するとその箇所は消えるので埋めなくても大丈夫です。
選択して保存するとこんな感じになります
DefaultIntent
まずは最初に呼ばれるDefaultWelcomeIntentというトリガー的なものを設定します 左のIntentsを選んだあとに default welcome intent を選んでください
その後、GoogleAssistantで起動された時にこのdefault welcome intent が起動するように、Eventsのところに Google_ASSISTANT_WELCOME を入力してください
次に受け答えを設定します UserSaysのところに「こんにちは」 textResponseのところに「こんにちは!フリーブックスです!」 と入力してください
ここで、一度動くかテストをしてみましょう 画面の右端にある Try it now に「こんにちは」と入力してみてください
すると先ほど入力しように「こんにちは!フリーブックスです!」とのレスポンスが返ってきます。
このように、API.AIではユーザーの入力とそのレスポンスを事前に定義しておくことでBotを作ることができます。
しかし、この侭では事前にレスポンスを全て用意しておかなければならず大変です。 そこでAPI.AIのwebhook機能を使います。
CloudFunction の利用
ここで一旦API.AIはおいて、cloudfunctionに移ります
Actions on Google を参考にして ApiAiApp を作って行きます
Build Fulfillment | Actions on Google | Google Developers
コードを貼るので詳しい説明は省きますが、基本的にやっていることは
- appを引数に取るIntentメソッドを作る
- Intentメソッドの中でいい感じにレスポンス文言を返すようにする
- Actionと呼ばれる、後々API.AIで定義する文字列とIntentメソッドを関連づける
というだけです。 GoogleHome とそれ以外の端末でレスポンスを分けたりとか、カルーセルの使い方とかを知りたいって方はここを読んでください
'use strict'; /* * Modules */ const App = require('actions-on-google').ApiAiApp; const functions = require('firebase-functions'); const request = require('request'); const cheerio = require('cheerio') /* * Constants * 定数系 */ const BASE_URL = 'https://www.amazon.co.jp/'; const Status = { welcome: 0 , showBooks: 1 , selectedBook: 2 } const Actions = { WELCOME: 'welcome', SHOW_BOOKS: 'show.books', CONFIRM: 'confirm', SELECTED_BOOK: 'selected.book', } let options = { method: 'GET', url: BASE_URL, encoding: 'UTF-8', headers: { 'Content-Type': 'text/plain;charset=utf-8', 'User-Agent': 'Mozilla/5.0(Linux;Android6.0;Nexus5Build/MRA58N)AppleWebKit/537.36(KHTML,likeGecko)Chrome/61.0.3163.100MobileSafari/537.36' } } const books = [{ url: 'https://www.amazon.co.jp//gp/aw/d/B00BB1ZSJA?ref_=msw_best_freehome_0&storeType=ebooks', title: 'レッツ☆ラグーン(1) (ヤングマガジンコミックス)', image: 'https://images-fe.ssl-images-amazon.com/images/I/51tbQJsZxLL._BG0,0,0,0_FMpng_SX300_.jpg' }, { url: 'https://www.amazon.co.jp//gp/aw/d/B075NKCVKD?ref_=msw_best_freehome_1&storeType=ebooks', title: 'ゴールデンカムイ【期間限定無料】 1 (ヤングジャンプコミックスDIGITAL)', image: 'https://images-fe.ssl-images-amazon.com/images/I/618tG-9ZD0L._BG0,0,0,0_FMpng_SX300_.jpg' }, { url: 'https://www.amazon.co.jp//gp/aw/d/B00BML5V6E?ref_=msw_best_freehome_2&storeType=ebooks', title: '亜人(1) (アフタヌーンコミックス)', image: 'https://images-fe.ssl-images-amazon.com/images/I/51VKi59sPPL._BG0,0,0,0_FMpng_SX300_.jpg' }, { url: 'https://www.amazon.co.jp//gp/aw/d/B075NL61FD?ref_=msw_best_freehome_3&storeType=ebooks', title: 'ゴールデンカムイ【期間限定無料】 2 (ヤングジャンプコミックスDIGITAL)', image: 'https://images-fe.ssl-images-amazon.com/images/I/61mxKIeQYDL._BG0,0,0,0_FMpng_SX300_.jpg' }, { url: 'https://www.amazon.co.jp//gp/aw/d/B075TY9SD3?ref_=msw_best_freehome_4&storeType=ebooks', title: 'アヤメくんののんびり肉食日誌(1)【期間限定 無料お試し版】 (FEEL COMICS)', image: 'https://images-fe.ssl-images-amazon.com/images/I/513a5UTtm9L._BG0,0,0,0_FMpng_SX300_.jpg' }, { url: 'https://www.amazon.co.jp//gp/aw/d/B00KCL6Y2U?ref_=msw_best_freehome_5&storeType=ebooks', title: 'いぬやしき(1) (イブニングコミックス)', image: 'https://images-fe.ssl-images-amazon.com/images/I/51YEu609hIL._BG0,0,0,0_FMpng_SX300_.jpg' }, { url: 'https://www.amazon.co.jp//gp/aw/d/B075J8HKBZ?ref_=msw_best_freehome_6&storeType=ebooks', title: 'うわばみ彼女【期間限定無料版】 1 (ジェッツコミックス)', image: 'https://images-fe.ssl-images-amazon.com/images/I/51S8gTApfPL._BG0,0,0,0_FMpng_SX300_.jpg' }, { url: 'https://www.amazon.co.jp//gp/aw/d/B00UN48X98?ref_=msw_best_freehome_7&storeType=ebooks', title: '本当にあった笑える話【無料連載版】', image: 'https://images-fe.ssl-images-amazon.com/images/I/61XtF2XgZxL._BG0,0,0,0_FMpng_SX300_.jpg' }, { url: 'https://www.amazon.co.jp//gp/aw/d/B00DW4ZYBG?ref_=msw_best_freehome_8&storeType=ebooks', title: '宝石の国(1) (アフタヌーンコミックス)', image: 'https://images-fe.ssl-images-amazon.com/images/I/61FXZ-+RZLL._BG0,0,0,0_FMpng_SX300_.jpg' }] /* * Model * sessionの間値を維持しておくための仕組み */ function initData(app) { let data = app.data; if(!data.books) { data.books = { data: [], page: 0 } } if(!data.status) { data.status = Status.welcome } return data; }; /* * Intents * API.AI 呼び出されるIntent */ function showBooksIntent(app) { console.log('called showBooksIntent action') initData(app) app.data.status = Status.showBooks if (app.hasSurfaceCapability(app.SurfaceCapabilities.SCREEN_OUTPUT)) { console.log('*** has screen ***') let response = '本日のおすすめの無料コミックはこちらです。' const items = books.map((book) => { const item = app.buildOptionItem(book.title, book.title) item.setTitle(book.title) item.setImage(book.image, book.title) return item }) const carousel = app.buildCarousel().addItems(items) app.data.books.data = books app.askWithCarousel(response, carousel) return } else { console.log('*** do not has screen ***') const offset = i * 3 if(offset + 3 > books.length) return app.tell('申し訳ございません。おすすめコミックが見つかりませんでした。') let response = '本日無料のおすすめコミックは「' + books[offset] + '」「' + books[offset + 1] + '」「' + books[offset+ 2] + '」' response += '他のコミックを探しますか?' app.ask(response); return } } function confirmIntent(app) { console.log('called showMoreIntent action') initData(app) if(app.data.status == Status.showBooks) { app.data.books.page += 1 showBooksIntent(app) return } else { app.tell('申し訳ございません。よくわかりませんでした。'); } } function selectedBookIntent(app) { console.log('called selectedBookIntent action') initData(app) app.data.status = Status.SELECTED_BOOK const book = app.data.books.data.find(book => { return book.title == app.getSelectedOption() }) if(!book) return app.tell('申し訳ございません。通信に失敗致しました。しばらくしてからもう一度御利用ください。'); const response = app.buildRichResponse() response.addSimpleResponse(book.title + 'ですね') const card = app.buildBasicCard(book.title) .setTitle(book.title) .setImage(book.image, book.title) .addButton('コミックを入手する', book.url) response.addBasicCard(card) app.tell(response) } /* * Mapping * 実際に cloud functions に公開するメソッド * API.AIからwebhookでこのメソッドが毎回呼ばれる * Actions on Google SDK を使ってactions名とIntentメソッドの紐付けを行う */ const Functions = functions.https.onRequest((request, response) => { const app = new App({request, response}); console.log('Request headers: ' + JSON.stringify(request.headers)); console.log('Request body: ' + JSON.stringify(request.body)); const actionMap = new Map(); actionMap.set(Actions.WELCOME, showBooksIntent); actionMap.set(Actions.SHOW_BOOKS, showBooksIntent); actionMap.set(Actions.CONFIRM, confirmIntent) actionMap.set(Actions.SELECTED_BOOK, selectedBookIntent) app.handleRequest(actionMap); }) module.exports = { Functions }
デプロイとAPI.AIとの紐付け
ここを参照するといい https://developers.google.com/actions/apiai/deploy-fulfillment
以下のことをやる
$ npm install -g firebase-tools $ firebase login $ firebase init $ npm install $ firebase deploy --only functions
deployが終わると console に
Function URL (Functions): https://hogehogehogehoge
みたいのが出てくるのでこれをコピーしておく
API.AIに戻り、 左端のFulfillmentをクリックしてDisabledになっている項目をEnabledにする そうするとwebhookを設定できるのでURLに先ほどの FUNCTION URL を入力する
Actionの登録
先ほどコード上でこのように定義したActionを実際にAPI.AIにも定義して、API.AIとコードを紐づける
const Actions = { WELCOME: 'welcome', SHOW_BOOKS: 'show.books', CONFIRM: 'confirm', SELECTED_BOOK: 'selected.book', }
API.AI左端の intents の+ボタンを押して、intentを新規作成 その後はuser says, action, を設定する
user says には 「おすすめのコミックを教えて」 action には selected.book を記入した
また、一番下に fulfillment -> use webhook というチェックボックスがあるのでチェックを入れる
これでIntentの定義完了
おなじようにSHOW_BOOKSのintentも作る
さっきと同じように設定して行くのだが、唯一違うのはEventsにactions_intent_OPTION
を設定する点
これをするとリストなどの要素から選択されたときにそのインテントが呼ばれるようになる
これが設定し終わったら完成
テスト
左の integrations -> google assistant を選択し、upload draft をする
そうすると actions on google の console に飛ばされて、アプリの情報の入力を迫られる
とりあえず全部埋めておく
infomationの入力が終わったら左のタブのsimulatorという項目を選ぶ
あとは試す
こんなかんじになる
https://photos.app.goo.gl/w4ckVZVEH00cXRaO2
Google Play とかと違ってデベロッパーアカウントを購入する必要はなさそう とりあえずできたぜ。リリースもしてみたぜ。
審査通るといいな
Actions-on-Google SDK を使う時にApiAiAppが読み込めない問題が起きた時の対処法
GoogleAssistant app を作るために node で actionsdkを読み込んだときに起きた問題
undefined:1 undefined ^ SyntaxError: Unexpected token u in JSON at position 0 at JSON.parse () at ApiAiApp.AssistantApp (/data/app/node_modules/actions-on-google/assistant-app.js:128:23) at ApiAiApp (/data/app/node_modules/actions-on-google/api-ai-app.js:78:5) at Server. (/data/app/app.js:16:13) at Server. (/data/app/node_modules/engine.io/lib/server.js:472:22) at Server. (/data/app/node_modules/socket.io/lib/index.js:307:16) at emitTwo (events.js:125:13) at Server.emit (events.js:213:7) at parserOnIncoming (_http_server.js:602:12) at HTTPParser.parserOnHeadersComplete (_http_common.js:116:23)
よくわからんけどこんな感じのエラーが起きる ライブラリの内部で使っているパーサー周りでなんか問題があるっぽい
調べて見たらbody-parser
を使ったらうまくいったよって書いてあったのでその通りにしたらうまく行った
const bodyParser = require('body-parser'); const app = express(); app.use(bodyParser.json());
海外で買ったgooglehomeを日本語対応させる方法
遂に日本でもグーグルホームがリリースされましたね
そして、海外で買った奴らも日本で使えるようになりましたよ
やり方は簡単でグーグルホームアプリを更新して、グーグルホームの設定をやり直して、言語設定を日本語にするだけです
超簡単だけど、設定を一回やり直さなきゃなのを忘れてたので少し時間を無駄にしてしまいました
日本語対応は嬉しいけど、新しい起動コマンドの
「ねえ、グーグル」
はダサい気がするなぁ笑
ReactNativeのFlatListがsetStateをしてもリレンダーされない時の対処法
FlatListのItemの中でFlatListで定義したdata以外のstateの値を使ってコンポネントをレンダーしていると、setState
を使ってもリレンダーされないことがある
そんなときはFlatListのExtraDataにその値を渡してあげるといい
そうするとextraDataの値が変更された時もrerenderされるようになる
<FlatList data={this.state.tags} extraData={this.state.movie} keyExtractor={this.defaultKeyExtractor} renderItem={this.renderTag} />
それでも動かない時もたまにある
そういう時はremoveClippedSubviews={false}
を追加してあげる
ここを参考にしてやった github.com