17
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2019-01-27

はじめに

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

17
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?