はじめに
Alexaスキルを作成する際、2019年1月までは、「Amazon Developer Portal(Amazon.co.jpアカウント)」と「AWSアカウント」の2つを用意して、Alexaスキルを開発する必要がありましたが、2019年1月24日のAlexa Blogの発表以降、「Amazon Developer Portal(Amazon.co.jpアカウント)」のみを用意すれば、Alexaスキルを作成することができるようになりました。
今回は、簡易的に、Alexa-hostedスキルでセッション永続性(S3)とメディアファイル(S3ファイル)を利用する方法を、サンプルソースを公開することで、お伝えしようと思います。
参考文献
Alexaスキルの変更点
- 「スキル呼び出し名」を、「とっさの挨拶」に変更しています。
コードエディタでの修正対象ファイル
- index.js
- package.json
S3画像の用意
- S3バケット内のMediaフォルダ内に任意の画像をアップする必要があります。
- 画像アップロードの際に、パブリックアクセス権限つけず、そのままアップロードする必要があります。
- Amazon Developer Portal内の「コードエディタ」メニューから、「Media storage: S3」をクリックし、「バケット名/Media」フォルダに「icon_512_A2Z.png(任意ファイル)」ファイルを保存して利用します。
- S3画像のみ利用する場合は、package.jsonファイルの修正は、必ずしも必要ないと思います。
セッション永続性(S3)利用時のポイント
- package.jsonファイルを修正し、"dependencies"項目の値に"ask-sdk-s3-persistence-adapter": "^2.0.0"を追記する必要があります。
おことわり
- Alexa-hostedスキルを使用してスキルをエンドツーエンドで作成するの参考内容を元に、index.jsサンプルソースを作成しています。
- セッション永続性(S3)を、有効的に利用するためには、もっとプログラムを工夫する必要があります。プログラムの工夫は、みなさまにお任せいたします。
index.jsサンプルソース
index.js
/* eslint-disable func-names */
/* eslint-disable no-console */
// 参考サイト:https://developer.amazon.com/ja/docs/hosted-skills/build-a-skill-end-to-end-using-an-alexa-hosted-skill.html
// 参考サイト:https://gist.github.com/germanviscuso/70c979f671660fea811ccfb63801f936
// 参考サイト:https://dev.classmethod.jp/cloud/aws/ask-sdk-for-node-js-s3-persistence-adapter/
// 参考サイト:https://dev.classmethod.jp/cloud/ask-sdk-s3-persistence-adapter/
// 参考サイト:https://dev.classmethod.jp/cloud/alexa-sdk-v2-fourth/
/**
* モジュール参照
*/
const Alexa = require('ask-sdk-core');
const Util = require('util.js');
// ask persistence adapterの読み込み
const persistenceAdapter = require('ask-sdk-s3-persistence-adapter');
/**
* LaunchRequestHandler
* ユーザ発話:「<スキル名>を開いて」
*/
const LaunchRequestHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'LaunchRequest';
},
async handle(handlerInput) {
const { attributesManager } = handlerInput;
// 永続化情報の取得
const s3Attributes = await attributesManager.getPersistentAttributes() || {};
let counterMessage = '';
// 訪問回数にあわせた応答メッセージの設定
if (!s3Attributes.counter) {
s3Attributes.counter = 1;
counterMessage = '初めてのご利用ありがとうございます。このスキルでは、';
} else {
s3Attributes.counter += 1;
counterMessage = `${s3Attributes.counter}回目のご利用感謝です。では、`;
}
// TODO: S3バケット内のMediaフォルダに任意の画像をアップする必要があります。
// 署名済み画像URL取得
const pictureUrl = Util.getS3PreSignedUrl('Media/icon_512_A2Z.png');
// S3永続化保存
attributesManager.setPersistentAttributes(s3Attributes);
await attributesManager.savePersistentAttributes();
const speechText = `とっさの挨拶へようこそ。${counterMessage}英語のあいさつの練習をおこないます。<voice name="Joanna"><lang xml:lang="en-US">Hello.</lang></voice>とおっしゃってください。`;
return handlerInput.responseBuilder
.speak(speechText)
.reprompt(speechText)
.withStandardCard('とっさの挨拶', '英語であいさつしてね!', pictureUrl)
.getResponse();
}
};
/**
* HelloWorldIntentHandler
* ユーザ発話:「ハロー」
*/
const HelloWorldIntentHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'IntentRequest'
&& handlerInput.requestEnvelope.request.intent.name === 'HelloWorldIntent';
},
async handle(handlerInput) {
const { attributesManager } = handlerInput;
const s3Attributes = await attributesManager.getPersistentAttributes() || {};
console.log('s3Attributes is: ', s3Attributes);
// const counter = s3Attributes.hasOwnProperty('counter') ? s3Attributes.counter : 0;
const counter = Object.prototype.hasOwnProperty.call(s3Attributes, 'counter') ? s3Attributes.counter : 1;
const speechText = `お上手ですね、こんにちは。保存したカウンターは${counter}です。`;
return handlerInput.responseBuilder
.speak(speechText)
// .reprompt('add a reprompt if you want to keep the session open for the user to respond')
.getResponse();
}
};
/**
* HelpIntentHandler
* ユーザ発話:「ヘルプ」
*/
const HelpIntentHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'IntentRequest'
&& handlerInput.requestEnvelope.request.intent.name === 'AMAZON.HelpIntent';
},
handle(handlerInput) {
const speechText = 'このスキルでは、英語のあいさつの練習をおこないます。ハローとおっしゃってください。';
return handlerInput.responseBuilder
.speak(speechText)
.reprompt(speechText)
.getResponse();
}
};
/**
* CancelAndStopIntentHandler
* ユーザ発話:「キャンセル」、「止めて」
*/
const CancelAndStopIntentHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'IntentRequest'
&& (handlerInput.requestEnvelope.request.intent.name === 'AMAZON.CancelIntent'
|| handlerInput.requestEnvelope.request.intent.name === 'AMAZON.StopIntent');
},
handle(handlerInput) {
const speechText = '終了を受け付けました。ご利用ありがとうございます。';
return handlerInput.responseBuilder
.speak(speechText)
.getResponse();
}
};
/**
* CancelAndStopIntentHandler
* ユーザ発話:「終了」
*/
const SessionEndedRequestHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'SessionEndedRequest';
},
handle(handlerInput) {
// Any cleanup logic goes here.
return handlerInput.responseBuilder.getResponse();
}
};
/**
* IntentReflectorHandler
* ユーザ発話:「<カスタムインテントのサンプル発話(デバッグ用)>」
*/
const IntentReflectorHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'IntentRequest';
},
handle(handlerInput) {
const intentName = handlerInput.requestEnvelope.request.intent.name;
const speechText = `デバッグモードです。${intentName}が呼び出されました。`;
return handlerInput.responseBuilder
.speak(speechText)
// .reprompt('add a reprompt if you want to keep the session open for the user to respond')
.getResponse();
}
};
/**
* ErrorHandler
* エラーハンドラ
*/
const ErrorHandler = {
canHandle() {
return true;
},
handle(handlerInput, error) {
console.log(`~~~~ Error handled: ${error.message}`);
const speechText = 'ちょっと分かりませんでした。ハローとおっしゃってください。';
return handlerInput.responseBuilder
.speak(speechText)
.reprompt(speechText)
.getResponse();
}
};
// アダプターを使用してスキルビルダーを初期化
const skillBuilder = Alexa.SkillBuilders.custom().withPersistenceAdapter(
new persistenceAdapter.S3PersistenceAdapter({ bucketName: process.env.S3_PERSISTENCE_BUCKET })
);
/**
* exports.handler
* メイン処理
*/
exports.handler = skillBuilder
.addRequestHandlers(
LaunchRequestHandler,
HelloWorldIntentHandler,
HelpIntentHandler,
CancelAndStopIntentHandler,
SessionEndedRequestHandler,
IntentReflectorHandler
)
.addErrorHandlers(ErrorHandler)
.lambda();
package.jsonサンプルソース
package.json
{
"name": "hello-world",
"version": "0.9.0",
"description": "alexa utility for quickly building skills",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Amazon Alexa",
"license": "ISC",
"dependencies": {
"ask-sdk-core": "^2.0.7",
"ask-sdk-model": "^1.4.1",
"aws-sdk": "^2.326.0",
"ask-sdk-s3-persistence-adapter": "^2.0.0"
}
}
おわりに
みなさん、いかがでしたでしょうか?
30分もあれば、Alexaスキル「Hello World」が完成するかと思います。
記事に誤り、不備等がございましたら、コメントにてご指摘いただけると幸いです。
2019/01/27 TAKAHIRO NISHIZONO