Alexaの自由入力を超丁寧に解説

# はじめに
初めてAlexaスキルを作ってみたのですが、少し苦労した点があったのでまとめておきます。それは自由入力です。自分のしゃべった言葉をtext化して処理したいとき、ありますよね。

「聞き取った言葉をまるごと変数に格納すればいいじゃん」と思うところですがこれが少し複雑だったんです。今回は実際にオウム返しをしてくれるスキルを作りながら説明していきます。

# 環境
[amazon developerコンソール](https://developer.amazon.com/dashboard)からAlexa skills kitで開発を進めていきます。

https://developer.amazon.com/dashboard

#### スキルを作成
1. スキルに追加するモデル
    カスタム
2. スキルのバックエンドリソース
    Alexa-hosted (Node.js)
3. スキルに追加するテンプレート
    スクラッチ
![messageImage_1663945676992.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2293026/539e8777-c0c5-c723-fb22-c12bbfe16707.jpeg)

# ちょっと解説
Alexaはユーザーが言うであろう言葉をテンプレートとして用意し待機しています。例えば好きな食べ物を聞き取る場合はこんな感じ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2293026/84eb36ba-109c-56c1-b675-d9675474dd85.png)
これによりAlexaは高い精度で人間の言葉を聞き取ることができるわけです。一方でそもそも人間が言ったことをまるごとtext化するという使い方は想定されていないことが分かります。

ではどうすればよいのでしょうか？実はそんなに難しくありません。

「今からオウム返ししてほしい言葉を言うからここに格納しろよ？」という箱を用意すればよいのです。実際にはこんな感じ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2293026/0cf95240-e6dc-a23b-050c-ed1be959c3b0.png)
では実際にやってみましょう。

# インテントの定義
ビルド→対話モデル→インテント からインテントを追加します。
![messageImage_1663948047286.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2293026/f8a234be-3252-1f6c-e3a0-7dcc59aad2de.jpeg)
サンプル発話とインテントスロットを追加しましょう。スロットタイプはAMAZON.SearchQuery を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2293026/a6693292-380e-cc92-fcee-ba43f8cfe358.png)
「一括編集」→「送信」
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2293026/8184a11a-9e7d-9ee1-528f-30b3ad659a92.png)


次に「ダイアログを編集」からスロット入力をオンにします。以下のように入力ましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2293026/7e82540a-b81d-f345-683a-278e163798b7.png)
そしてもう一度インテントの画面に戻って「一括編集」→「送信」をしてください。

ここまで出来たら「モデルを保存」→「モデルをビルド」を忘れずに！！

# コーディング
 コードエディタタブを開きましょう。ここからindex.jsを編集していきます。
![messageImage_1663949625598.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2293026/22012309-c6db-fae9-9b75-5d2ef19357cf.jpeg)
まずLaunchRequestHandlerのspeakOutputを書き換えます。
```js:index.js
const LaunchRequestHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'LaunchRequest';
    },
    handle(handlerInput) {
        const speakOutput = 'リピートくんを起動しました。';//ここを書き換える

        return handlerInput.responseBuilder
            .speak(speakOutput)
            .reprompt(speakOutput)
            .getResponse();
    }
};
```


次に以下のインテントハンドラーを書き足してください。
```js:index.js
const RepeatIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'RepeatldIntent';
    },
    handle(handlerInput) {
        //ユーザーが言った言葉はここに格納される
        const speakOutput = handlerInput.requestEnvelope.request.intent.slots.message.value;

        return handlerInput.responseBuilder
            .speak(speakOutput)
            .getResponse();
    }
};
```
最後に一番下のexport.handlerに先ほどのRepeatIntentHandlerを書き足しましょう。
```js:index.js
exports.handler = Alexa.SkillBuilders.custom()
    .addRequestHandlers(
        LaunchRequestHandler,
        HelloWorldIntentHandler,
        RepeatIntentHandler, //←new!!
        HelpIntentHandler,
        CancelAndStopIntentHandler,
        FallbackIntentHandler,
        SessionEndedRequestHandler,
        IntentReflectorHandler)
    .addErrorHandlers(
        ErrorHandler)
    .withCustomUserAgent('sample/hello-world/v1.2')
    .lambda();
```

これで完成！！
「保存」→「デプロイ」をしましょう！！




コード全体は以下の通りです。
```js:index.js
/* *
 * This sample demonstrates handling intents from an Alexa skill using the Alexa Skills Kit SDK (v2).
 * Please visit https://alexa.design/cookbook for additional examples on implementing slots, dialog management,
 * session persistence, api calls, and more.
 * */
const Alexa = require('ask-sdk-core');

const LaunchRequestHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'LaunchRequest';
    },
    handle(handlerInput) {
        const speakOutput = 'Welcome, you can say Hello or Help. Which would you like to try?';

        return handlerInput.responseBuilder
            .speak(speakOutput)
            .reprompt(speakOutput)
            .getResponse();
    }
};

const HelloWorldIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'HelloWorldIntent';
    },
    handle(handlerInput) {
        const speakOutput = 'Hello World!';

        return handlerInput.responseBuilder
            .speak(speakOutput)
            //.reprompt('add a reprompt if you want to keep the session open for the user to respond')
            .getResponse();
    }
};

const RepeatIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'RepeatldIntent';
    },
    handle(handlerInput) {
        //ユーザーが言った言葉はここに格納される
        const speakOutput = handlerInput.requestEnvelope.request.intent.slots.message.value;

        return handlerInput.responseBuilder
            .speak(speakOutput)
            .getResponse();
    }
};

const HelpIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.HelpIntent';
    },
    handle(handlerInput) {
        const speakOutput = 'You can say hello to me! How can I help?';

        return handlerInput.responseBuilder
            .speak(speakOutput)
            .reprompt(speakOutput)
            .getResponse();
    }
};

const CancelAndStopIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && (Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.CancelIntent'
                || Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.StopIntent');
    },
    handle(handlerInput) {
        const speakOutput = 'Goodbye!';

        return handlerInput.responseBuilder
            .speak(speakOutput)
            .getResponse();
    }
};
/* *
 * FallbackIntent triggers when a customer says something that doesn’t map to any intents in your skill
 * It must also be defined in the language model (if the locale supports it)
 * This handler can be safely added but will be ingnored in locales that do not support it yet 
 * */
const FallbackIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.FallbackIntent';
    },
    handle(handlerInput) {
        const speakOutput = 'Sorry, I don\'t know about that. Please try again.';

        return handlerInput.responseBuilder
            .speak(speakOutput)
            .reprompt(speakOutput)
            .getResponse();
    }
};
/* *
 * SessionEndedRequest notifies that a session was ended. This handler will be triggered when a currently open 
 * session is closed for one of the following reasons: 1) The user says "exit" or "quit". 2) The user does not 
 * respond or says something that does not match an intent defined in your voice model. 3) An error occurs 
 * */
const SessionEndedRequestHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'SessionEndedRequest';
    },
    handle(handlerInput) {
        console.log(`~~~~ Session ended: ${JSON.stringify(handlerInput.requestEnvelope)}`);
        // Any cleanup logic goes here.
        return handlerInput.responseBuilder.getResponse(); // notice we send an empty response
    }
};
/* *
 * The intent reflector is used for interaction model testing and debugging.
 * It will simply repeat the intent the user said. You can create custom handlers for your intents 
 * by defining them above, then also adding them to the request handler chain below 
 * */
const IntentReflectorHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest';
    },
    handle(handlerInput) {
        const intentName = Alexa.getIntentName(handlerInput.requestEnvelope);
        const speakOutput = `You just triggered ${intentName}`;

        return handlerInput.responseBuilder
            .speak(speakOutput)
            //.reprompt('add a reprompt if you want to keep the session open for the user to respond')
            .getResponse();
    }
};
/**
 * Generic error handling to capture any syntax or routing errors. If you receive an error
 * stating the request handler chain is not found, you have not implemented a handler for
 * the intent being invoked or included it in the skill builder below 
 * */
const ErrorHandler = {
    canHandle() {
        return true;
    },
    handle(handlerInput, error) {
        const speakOutput = 'Sorry, I had trouble doing what you asked. Please try again.';
        console.log(`~~~~ Error handled: ${JSON.stringify(error)}`);

        return handlerInput.responseBuilder
            .speak(speakOutput)
            .reprompt(speakOutput)
            .getResponse();
    }
};

/**
 * This handler acts as the entry point for your skill, routing all request and response
 * payloads to the handlers above. Make sure any new handlers or interceptors you've
 * defined are included below. The order matters - they're processed top to bottom 
 * */
exports.handler = Alexa.SkillBuilders.custom()
    .addRequestHandlers(
        LaunchRequestHandler,
        HelloWorldIntentHandler,
        RepeatIntentHandler, //←new!!
        HelpIntentHandler,
        CancelAndStopIntentHandler,
        FallbackIntentHandler,
        SessionEndedRequestHandler,
        IntentReflectorHandler)
    .addErrorHandlers(
        ErrorHandler)
    .withCustomUserAgent('sample/hello-world/v1.2')
    .lambda();
```

# テスト
テストタブからAlexaスキルとのやり取りをシミュレーションすることができます。実際にやってみましょう。
![messageImage_1663951362650.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2293026/904320bd-05a8-2644-1c0e-a281b5de7632.jpeg)
できていますね！無事にAlexaにオウム返しさせることに成功しました！！


# 最後に

いかがでしたか？
ぜひ皆さんもオリジナルのAlexaスキルを作ってみてください！！