Alexa Developer スキルアワード2019 ハッカソン東京 Vol.2 でリマインダーAPIを使ったスキルを作ったところ、「リマインダーを使ってみたい!」というフィードバックをたくさんいただきました。この記事が皆さんのお役に立てば光栄です!
プロダクトに組み込む前に、ぜひお試しスキルを作って動作を確認してみてください。ハマりポイントがいくつかあります。
前提
- Alexa-hostedスキルで作ります。
- Amazon 開発者アカウントさえあればスキル開発できます。AWS アカウント不要。
- Web ブラウザーだけで開発できます。Windows PC でもOK。
- 詳しくは公式ドキュメント Alexa-hostedスキルを使用してスキルをエンドツーエンドで作成する
- 指定秒数後にリマインドします。相対時刻を使うやり方(SCHEDULED_RELATIVE)です。
- 絶対時刻を指定するやり方は、公式ドキュメントのリマインダーの作成を参照してください。type: "SCHEDULED_ABSOLUTE" のところです。
参考情報
- 公式ドキュメント
- 公式リポジトリのサンプルコード(英語)
- Alexa Blogs
- Alexaスキル開発トレーニングシリーズ:リマインダーAPIの利用方法
- 上記サンプルコードの解説です。
開発するスキル
指定した秒数が経つとリマインドします。
- ユーザー「アレクサ、お試しリマインダーを開いて」
- Alexa「何秒後にリマインドしますか?」
- ユーザー「10秒後」
- Alexa「分かりました。10秒後にリマインドします。」
10秒たつと、スマホに通知が来ます。実機だと音が鳴り、Alexaがしゃべります。
手順
開発者コンソール「ビルド」タブでの作業
アクセス権限で、リマインダーを有効にする
開発者コンソールからスキルを選び、上部タブ「ビルド」>左ペイン「アクセス権限」>「リマインダー」をチェックします。
RemindIntent を作る
ReminderIntent を作ります。
インテントスロット sec を作ります。スロットタイプは AMAZON.NUMBER です。
そして「このインテントには確認が必要ですか?」にチェックを入れます(重要!)
「Alexaのプロンプト」には「{sec}後にリマインドしてもよろしいでしょうか?」と入力します。
忘れずに上部のボタン「モデルをビルド」を押しておきましょう。
もし「確認が必要」のチェックを入れ忘れると、「テスト」タブの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 に上げます。
"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 さん、動画ありがとうございます!
#Alexaスキルアワード
— TAKAHIRO NISHIZONO (@zono_0) June 23, 2019
Alexaスキル 7daysチャレンジ pic.twitter.com/2AjeQikUu8