13
10

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 スキルからリマインダー API を使う

Last updated at Posted at 2019-06-24

Alexa Developer スキルアワード2019 ハッカソン東京 Vol.2 でリマインダーAPIを使ったスキルを作ったところ、「リマインダーを使ってみたい!」というフィードバックをたくさんいただきました。この記事が皆さんのお役に立てば光栄です!

プロダクトに組み込む前に、ぜひお試しスキルを作って動作を確認してみてください。ハマりポイントがいくつかあります。

前提

  • Alexa-hostedスキルで作ります。
  • 指定秒数後にリマインドします。相対時刻を使うやり方(SCHEDULED_RELATIVE)です。
    • 絶対時刻を指定するやり方は、公式ドキュメントのリマインダーの作成を参照してください。type: "SCHEDULED_ABSOLUTE" のところです。

参考情報

開発するスキル

指定した秒数が経つとリマインドします。

  • ユーザー「アレクサ、お試しリマインダーを開いて」
  • Alexa「何秒後にリマインドしますか?」
  • ユーザー「10秒後」
  • Alexa「分かりました。10秒後にリマインドします。」

10秒たつと、スマホに通知が来ます。実機だと音が鳴り、Alexaがしゃべります。

手順

開発者コンソール「ビルド」タブでの作業

アクセス権限で、リマインダーを有効にする

開発者コンソールからスキルを選び、上部タブ「ビルド」>左ペイン「アクセス権限」>「リマインダー」をチェックします。

RemindIntent を作る

ReminderIntent を作ります。

image.png

インテントスロット sec を作ります。スロットタイプは AMAZON.NUMBER です。

image.png

そして「このインテントには確認が必要ですか?」にチェックを入れます(重要!)
「Alexaのプロンプト」には「{sec}後にリマインドしてもよろしいでしょうか?」と入力します。

image.png

忘れずに上部のボタン「モデルをビルド」を押しておきましょう。

もし「確認が必要」のチェックを入れ忘れると、「テスト」タブのAlexaシミュレーター、スキルI/OのJSON入力に、以下のエラーが表示されます。

	"request": {
		"type": "SessionEndedRequest",
... 
		"error": {
			"type": "INVALID_RESPONSE",
			"message": "The following directives are not supported: DelegateDirective"
		}
	}

開発者コンソール「コードエディタ」タブでの作業

package.json

ask-sdk-core を 2.3.0 に上げます。

package.json
  "dependencies": {
    "ask-sdk-core": "^2.3.0",
    "ask-sdk-model": "^1.11.1",
...
  }

これを忘れると、実行時のログ CloudWatch に以下のようなエラーが表示されます。

handlerInput.serviceClientFactory.getReminderManagementServiceClient is not a function

index.js

さぁ、いよいよコーディングです。

  • PERMISSIONS定数にリマインダーAPIを入れておく。
  • 起動したら「何秒後にリマインドしますか?」と話す。
  • RemindIntentHandler を追加する。
  • ErrorHandler を変更する。
  • export.handler に .withApiClient() を追加する。
...
const PERMISSIONS = ['alexa::alerts:reminders:skill:readwrite'];

const LaunchRequestHandler = {
    canHandle(handlerInput) {
        return handlerInput.requestEnvelope.request.type === 'LaunchRequest';
    },
    handle(handlerInput) {
        const speechText = '何秒後にリマインドしますか?';
        return handlerInput.responseBuilder
            .speak(speechText)
            .reprompt(speechText)
            .getResponse();
    }
};

const RemindIntentHandler = {
    canHandle(handlerInput) {
        return handlerInput.requestEnvelope.request.type === 'IntentRequest'
            && handlerInput.requestEnvelope.request.intent.name === 'RemindIntent';
    },
    // handle の前に async 追加
    async handle(handlerInput) {
        const sec = handlerInput.requestEnvelope.request.intent.slots.sec.value;
        const requestEnvelope = handlerInput.requestEnvelope;
        const responseBuilder = handlerInput.responseBuilder;
        const consentToken = requestEnvelope.context.System.apiAccessToken;

        // check for confirmation.  if not confirmed, delegate
        switch (requestEnvelope.request.intent.confirmationStatus) {
        case 'CONFIRMED':
            // intent is confirmed, so continue
            console.log('Alexa confirmed intent, so clear to create reminder');
            break;
        case 'DENIED':
            // intent was explicitly not confirmed, so skip creating the reminder
            console.log('Alexa disconfirmed the intent; not creating reminder');
            return responseBuilder
                .speak(`わかりました。リマインドしません。`)
                .getResponse();
        case 'NONE':
        default:
            console.log('delegate back to Alexa to get confirmation');
            return responseBuilder
                .addDelegateDirective()
                .getResponse();
        }

        if (!consentToken) {
            return responseBuilder
                .speak(`アレクサアプリにカードを送信しました。リマインダーの権限を有効にしてください。`)
                .withAskForPermissionsConsentCard(PERMISSIONS)
                .getResponse();
        }
        try {
            const client = handlerInput.serviceClientFactory.getReminderManagementServiceClient();
            const reminderRequest = {
                trigger: {
                    type: 'SCHEDULED_RELATIVE',  // 指定秒数後にリマインド
                    offsetInSeconds: sec,
                },
                alertInfo: {
                    spokenInfo: {
                        content: [{
                            locale: 'ja-JP', // 日本国の日本語
                            text: 'お時間です', // スマホに通知する文言
                        }],
                    },
                },
                pushNotification: {
                    status: 'ENABLED',
                },
            };
            const reminderResponse = await client.createReminder(reminderRequest);
            console.log(JSON.stringify(reminderResponse));
        } catch (error) {
            if (error.name !== 'ServiceError') {
                console.log(`error: ${error.stack}`);
                const response = responseBuilder.speak(`おっと。エラーが発生しました。`).getResponse();
                return response;
            }
            throw error;
        }
        return responseBuilder
            .speak(`分かりました。${sec}秒後にリマインドします。`)
            .getResponse();
    },
};

// HelpIntentHandler などはそのまま使う。

// ErrorHandler は書き換える
const ErrorHandler = {
  canHandle(handlerInput, error) {
    return error.name === 'ServiceError';
  },
  handle(handlerInput, error) {
    // console.log(`ERROR STATUS: ${error.statusCode}`);
    console.log(`ERROR MESSAGE: ${error.message}`);
    // console.log(`ERROR RESPONSE: ${JSON.stringify(error.response)}`);
    // console.log(`ERROR STACK: ${error.stack}`);
    switch (error.statusCode) {
      case 401:
        return handlerInput.responseBuilder
          .speak(`Alexaアプリのホーム画面で、スキルに権限を付与してください。`)
          .withAskForPermissionsConsentCard(PERMISSIONS)
          .getResponse();
      case 403:
        return handlerInput.responseBuilder
          .speak(`このデバイスではリマインダーを設定することができません。`)
          .getResponse();
      default:
        return handlerInput.responseBuilder
          .speak(`リマインダーの設定でエラーが発生しました。`)
          .getResponse();
    }
  },
};

exports.handler = Alexa.SkillBuilders.custom()
    .addRequestHandlers(
        LaunchRequestHandler,
        RemindIntentHandler,
        HelpIntentHandler,
        CancelAndStopIntentHandler,
        SessionEndedRequestHandler,
        IntentReflectorHandler)
    .addErrorHandlers(
        ErrorHandler)
    // 追加
    .withApiClient(new Alexa.DefaultApiClient())
    .lambda();

うまく動かなかった時のチェックリスト

次のことが正しくできているか、確認してください。

  • 開発コンソールのアクセス権限でリマインダーをチェックしている
  • package.json を修正して、ask-sdk-core のバージョンを上げてある
  • RemindIntent の「このインテントには確認が必要ですか?」をチェックしている
  • アレクサアプリで、リマインダーの権限を有効にしている

ハッカソンで作ったスキル

自宅で運動したくなるスキルです。毎日決まった時間になると、Alexaがリマインドしてくれます。
@zono_0 さん、動画ありがとうございます!

13
10
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
13
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?