スマートスピーカーAdventカレンダー18日目担当 @snaka です。投稿間に合わず、日付変わってしまいました。すみません。
自作のスキルはもっぱらPodcast再生スキルのみなので、必要最低限レベルの実装で動くPodcast再生スキルの作り方というテーマで記事を書いてみた。
例題として「Misreading Chat」というポッドキャストを再生するスキルを作ってみた。
この記事での作業手順は基本的に CLI ベースで作業する前提となっている。以下の記事も参考にすると良いかも。
この記事の手順を実行した結果は GitHub に公開しているので参考まで。
プロジェクト作成
まずは、ask new
でプロジェクトを作成する。
$ ask new
? Please select the runtime Node.js V8
? List of templates you can choose Hello World
? Please type in your skill name: skill-misreading-podcast
Skill "skill-misreading-podcast" has been created based on the chosen template
スキルのデプロイ
以下のように skill.json を編集
-
manifest.publishingInformation.locales
の下にja-JP
のエントリを作成 -
manifest.publishingInformation.category
をPODCAST
に -
manifest.apis.custom
にinterfaces
のエントリを記述
以下のようになっているはず。
{
"manifest": {
"publishingInformation": {
"locales": {
"ja-JP": {
"summary": "Misreading Chat",
"examplePhrases": [
"アレクサ、ミスリーディングチャットを開いて"
],
"name": "skill-misreading-podcast",
"description": "Sample Full Description"
}
},
"isAvailableWorldwide": true,
"testingInstructions": "Sample Testing Instructions.",
"category": "PODCAST",
"distributionCountries": []
},
"apis": {
"custom": {
"endpoint": {
"sourceDir": "lambda/custom",
"uri": "ask-custom-skill-misreading-podcast-default"
},
"interfaces": [
{ "type": "AUDIO_PLAYER" }
]
}
},
"manifestVersion": "1.0"
}
}
上記を保存したら、以下のコマンドでスキルのみをデプロイ
$ ask deploy -t skill
モデルをデプロイ
以下のように models
配下にJSONファイルを作成
-
invocationName
はスキルの呼び出し名
{
"interactionModel": {
"languageModel": {
"invocationName": "ミスリーディングチャット",
"intents": [
{
"name": "AMAZON.CancelIntent",
"samples": [
"キャンセル"
]
},
{
"name": "AMAZON.HelpIntent",
"samples": []
},
{
"name": "AMAZON.StopIntent",
"samples": []
},
{
"name": "AMAZON.PauseIntent",
"samples": []
},
{
"name": "AMAZON.ResumeIntent",
"samples": []
}
]
}
}
}
デプロイするためには sample
を1つ以上登録する必要があるらしいので、とりあえずキャンセル
という発話サンプルだけ登録している。
jsonを保存して以下のようにモデルをデプロイする。
$ ask deploy -t model
Lambda 関数のデプロイ設定
Lambda 関数デプロイの準備として以下のように serverless.yml を作成
-
alexaSkill
の値は自分のスキルのIDに置き換える-
ask deploy -t skill
の実行結果などからコピペする
-
service: skill-misreading-podcast
provider:
name: aws
region: ap-northeast-1
runtime: nodejs8.10
memorySize: 128
timeout: 30
package:
include:
- node_modules/
functions:
main:
handler: index.handler
events:
- alexaSkill: amzn1.ask.skill.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
enabled: true
必要なパッケージのインストール
後述のコードでrequest
, feedparser
を利用するため、以下のようにパッケージをインストールしておく。
$ npm install --save request feedparser
Lambda 関数を作成
index.js を以下のように実装。
-
PODCAST_FEED_URL
はポッドキャストのフィードURL -
PODCAST_NAME
はAlexaがエピソード再生前に読み上げるポッドキャスト名-
models/ja-JP.json
に定義した「スキル呼び出し名」とは別の名前を設定してもよい
-
/* eslint-disable func-names */
/* eslint-disable no-console */
const request = require('request')
const FeedParser = require('feedparser')
const PODCAST_FEED_URL = 'https://misreading.chat/category/episodes/feed/'
const PODCAST_NAME = 'ミスリーディングチャット'
function pickSslMediaUrl (enclosures) {
const sslMedia = enclosures.find(item => item.url.startsWith('https'))
if (sslMedia) return sslMedia.url
const nonSslMedia = enclosures[0]
// Alexa Skill の AudioPlayer は https: で提供されるURLしか対応していないため強引に書き換える
if (nonSslMedia) return nonSslMedia.url.replace(/^http:/, 'https:')
throw new Error('Media not found.')
}
function getLatestEpisode () {
return new Promise(async (resolve, reject) => {
const feedparser = new FeedParser()
request.get(PODCAST_FEED_URL).pipe(feedparser)
feedparser.on('data', (data) => {
const audioUrl = pickSslMediaUrl(data.enclosures)
resolve({
title: data.title,
url: audioUrl,
published_at: data.pubDate.toISOString()
})
})
})
}
async function playPodcast (handlerInput) {
const latestEpisode = await getLatestEpisode()
const speechText = `${PODCAST_NAME} の最新エピソード ${latestEpisode.title} を再生します`;
return handlerInput.responseBuilder
.speak(speechText)
.withSimpleCard(PODCAST_NAME, speechText)
.addAudioPlayerPlayDirective('REPLACE_ALL', latestEpisode.url, latestEpisode.url, 0)
.getResponse();
}
const Alexa = require('ask-sdk-core');
const LaunchRequestHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'LaunchRequest';
},
async handle(handlerInput) {
return await playPodcast(handlerInput)
},
};
const ErrorHandler = {
canHandle() {
return true;
},
handle(handlerInput, error) {
console.log(`Error handled: ${error.message}`);
return handlerInput.responseBuilder
.speak('ごめんなさい、よくわかりません')
.reprompt('ごめんなさい、よくわかりません')
.getResponse();
},
};
const skillBuilder = Alexa.SkillBuilders.custom();
exports.handler = skillBuilder
.addRequestHandlers(
LaunchRequestHandler,
)
.addErrorHandlers(ErrorHandler)
.lambda();
index.js を保存したら以下のように Lambda 関数をデプロイ
$ sls deploy
スキルのエンドポイントにLambda関数のARNを登録
Lambda 関数をデプロイしたら AWS の管理コンソールでデプロイした Lambda 関数を開き、関数のARNを控える。
そしてskill.json
を開き以下のように、manifest.apis.custom.endpoint.uri
の値として Lambda 関数のARNをセットする
"endpoint": {
"uri": "arn:aws:lambda:ap-northeast-1:xxxxxxxxxxxx:function:skill-misreading-podcast-dev-main"
},
上記を保存した後、スキルをデプロイ
$ ask deploy -t skill
スキルの呼び出しをテスト
まず、テストを有効化する
$ ask api enable-skill -s amzn1.ask.skill.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx --stage development
以下のようにスキルを呼び出してみる、-t
の後の文字列は、skill.json
に登録したスキル呼び出し名にする
$ ask simulate -l 'ja-JP' -t 'ミスリーディングチャットを開いて'
レスポンスで以下のようにAudioPlayer.Play
ディレクティブに、音源(mp3)のURLがセットされていたらOK
{
"type": "AudioPlayer.Play",
"playBehavior": "REPLACE_ALL",
"audioItem": {
"stream": {
"token": "https://misreadingchat.files.wordpress.com/2018/11/40.mp3",
"url": "https://misreadingchat.files.wordpress.com/2018/11/40.mp3",
"offsetInMilliseconds": 0
}
}
}
ベータテスト
シミュレータではAudioPlayerでの音声ファイルの再生は行えないので、音声を再生まで確認したい場合はベータテストとしてスキルを公開する必要がある。
その準備として、skill.json
を開き以下のように記述を追加
-
smallIconUri
,largeIconUri
にアイコンのURLを設定- アイコンは規定のサイズでないとデプロイでエラーになるので注意
- 下記の例で示しているアイコン画像はpublicに公開してるので自由に使ってもらってOK
- `privacyAndCompliance' のエントリを追加
最終的に skill.json
は以下のようになっているはず。
{
"manifest": {
"publishingInformation": {
"locales": {
"ja-JP": {
"summary": "Misreading Chat",
"examplePhrases": [
"アレクサ、ミスリーディング開いて"
],
"name": "skill-misreading-podcast",
"description": "Sample Full Description",
"smallIconUri": "https://s3-ap-northeast-1.amazonaws.com/snaka-public/alexa-icons/Sample_108.png",
"largeIconUri": "https://s3-ap-northeast-1.amazonaws.com/snaka-public/alexa-icons/Sample_512.png"
}
},
"isAvailableWorldwide": true,
"testingInstructions": "Sample Testing Instructions.",
"category": "PODCAST",
"distributionCountries": []
},
"privacyAndCompliance": {
"allowsPurchases": false,
"usesPersonalInfo": false,
"isChildDirected": false,
"isExportCompliant": true,
"containsAds": false
},
"apis": {
"custom": {
"endpoint": {
"uri": "arn:aws:lambda:ap-northeast-1:xxxxxxxxxxx:function:skill-misreading-podcast-dev-main"
},
"interfaces": [
{ "type": "AUDIO_PLAYER" }
]
}
},
"manifestVersion": "1.0"
}
}
上記を保存したらスキルをデプロイ
$ ask deploy -t skill
スキルのデプロイが完了したら、開発者ポータル上でベータテストの有効化を行う。
具体的な手順は以下のページなどを参照。
Alexaスキルのベータテストを行う | Custom Skills
ベータテストに参加して、スキルがEchoデバイスで有効になったら「アレクサ、ミスリーディングチャットを開いて」と発話することで Echo デバイスで Misreading Chat の最新エピソードが再生されるはず。
さいごに
ここで紹介したコードは、必要なインテントの実装が抜けていたりするので、そのままでは審査を通すことはできないのでご注意を。
また、第三者が権利を有するコンテンツをスキルで使用する場合、そのコンテンツを扱うための許諾を受けていることを審査時に提示しなければならない、そのあたりについては以前記事を書いているので下記を参照のこと。
現在、Podcast 対応スキルを最小限のコーディングで実装するためのパッケージを作成中なので、いい感じにできてきたら、そのうち Qiita にチュートリアルの記事をアップできたらいいなぁ(モチベーション次第... )