Edited at

Alexa-hostedスキルでセッション永続性(S3)とメディアファイル(S3ファイル)を利用する方法


はじめに

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"を追記する必要があります。


おことわり


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