Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
12
Help us understand the problem. What is going on with this article?
@zono_0

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

More than 1 year has passed since last update.

はじめに

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

12
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
zono_0
( ɵ̷̥̥᷄ .̠ ɵ̷̥̥᷅ ) バイク:Vストローム250 パソコン:Ryzen 2700x カメラ:PENTAX K-r 派 動画編集:DaVinci Resolve 15 Qiita:http://qiita.com/zono_0 川崎市近郊バイクツーリングマップ:http://bit.ly/2GGMc9J

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
12
Help us understand the problem. What is going on with this article?