AlexaのカスタムスキルをTypeScriptで書く
やってみたことの覚え書き。 AWS Lambda上で動かすAlexaのカスタムスキルをTypeScriptで書くにはどうすればよいか。
例として、「Alexaスキル開発トレーニングシリーズ 第1回 初めてのスキル開発」の「宇宙豆知識」をTypeScriptで書いてみます。
npmパッケージを追加する
javascriptでAWS Lambda上で動かすスキルを書く場合、 以下のnpmパッケージをインストールしていることでしょう。
- aws-sdk
- alexa-sdk
これに加えて、以下のTypeScript用の型情報パッケージをインストールします。
- @types/node
- @types/aws-lambda
- @types/alexa-sdk
"@types/node"はNode.jsの機能を使う場合に必要になります。 例えば、スキル実装の定石として、以下のようなコードがあります。 アプリケーションIDを環境変数"APP_ID"から読み込むものです。
const alexa = Alexa.handler(event, context); alexa.appId = process.env.APP_ID;
このprocessはNode.jsのオブジェクトですので、 「process.env」をTypeScript上で型解決するには"@types/node"が必要になります。
"@types/aws-lambda"はLambdaの機能を利用する場合に必要となります。 しかし、単純なスキルの実装では、 Lambdaの機能を明に利用することはほとんどないでしょうから、 不要かもしれません。
型を利用してプログラムする
型情報を利用して、 「Alexaスキル開発トレーニングシリーズ 第1回 初めてのスキル開発」の「宇宙豆知識」のindex.jsをTypeScriptで書くと、 以下のようになるんじゃないかな。
import * as Alexa from 'alexa-sdk'; //========================================================================================================================================= //TODO: このコメント行より下の項目に注目してください。 //========================================================================================================================================= //Replace with your app ID (OPTIONAL). You can find this value at the top of your skill's page on http://developer.amazon.com. //Make sure to enclose your value in quotes, like this: var APP_ID = "amzn1.ask.skill.bb4045e6-b3e8-4133-b650-72923c5980f1"; const APP_ID = undefined; const SKILL_NAME = "豆知識"; const GET_FACT_MESSAGE = "知ってましたか?"; const HELP_MESSAGE = "豆知識を聞きたい時は「豆知識」と、終わりたい時は「おしまい」と言ってください。どうしますか?"; const HELP_REPROMPT = "どうしますか?"; const STOP_MESSAGE = "さようなら"; //========================================================================================================================================= //「TODO: ここから下のデータを自分用にカスタマイズしてください。」 //========================================================================================================================================= const data = [ "水星の一年はたった88日です。", "金星は水星と比べて太陽より遠くにありますが、気温は水星よりも高いです。", "金星は反時計回りに自転しています。過去に起こった隕石の衝突が原因と言われています。", "火星上から見ると、太陽の大きさは地球から見た場合の約半分に見えます。", "木星の<sub alias='いちにち'>1日</sub>は全惑星の中で一番短いです。", "天の川銀河は約50億年後にアンドロメダ星雲と衝突します。", "太陽の質量は全太陽系の質量の99.86%を占めます。", "太陽はほぼ完璧な円形です。", "皆既日食は一年から二年に一度しか発生しない珍しい出来事です。", "土星は自身が太陽から受けるエネルギーの2.5倍のエネルギーを宇宙に放出しています。", "太陽の内部温度は摂氏1500万度にも達します。", "月は毎年3.8cm地球から離れていっています。" ]; //========================================================================================================================================= //この行から下のコードに変更を加えると、スキルが動作しなくなるかもしれません。わかる人のみ変更を加えてください。 //========================================================================================================================================= export const handler = function<T extends Alexa.Request>(event: Alexa.RequestBody<T>, context: Alexa.Context, callback?: (err: any, response: any) =< void) { const alexa = Alexa.handler(event, context); alexa.appId = APP_ID; alexa.registerHandlers(handlers); alexa.execute(); }; const handlers: Alexa.Handlers<Alexa.Request> = { 'LaunchRequest': function (this: Alexa.Handler<Alexa.LaunchRequest>): void { this.emit('GetNewFactIntent'); }, 'GetNewFactIntent': function (this: Alexa.Handler<Alexa.IntentRequest>): void { const factArr = data; const factIndex = Math.floor(Math.random() * factArr.length); const randomFact = factArr[factIndex]; const speechOutput = GET_FACT_MESSAGE + randomFact; this.emit(':tellWithCard', speechOutput, SKILL_NAME, randomFact) }, 'AMAZON.HelpIntent': function (this: Alexa.Handler<Alexa.IntentRequest>): void { const speechOutput = HELP_MESSAGE; const reprompt = HELP_REPROMPT; this.emit(':ask', speechOutput, reprompt); }, 'AMAZON.CancelIntent': function (this: Alexa.Handler<Alexa.IntentRequest>): void { this.emit(':tell', STOP_MESSAGE); }, 'AMAZON.StopIntent': function (this: Alexa.Handler<Alexa.IntentRequest>): void { this.emit(':tell', STOP_MESSAGE); } };
ポイントとしては、以下の通り。
- varをconstに変更しているけど、これはTypeScriptに限らず、今となってはほとんどのvarはconst/letにした方がよい。
- javascriptの"require('alexa-sdk')"はTypeScriptのimport文に変更する。
- 唯一エクスポートする"handler"に"export"指定を追加。
- 登録するハンドラを正しく型付ける。これにより、ハンドラ内の実装で型による検証が効くようになる。
で、TypeScriptで型付けを行った効果として、 潜在していたバグを見つけました。 上のソースで以下のように書いている部分、
alexa.appId = APP_ID;
「Alexaスキル開発トレーニングシリーズ 第1回 初めてのスキル開発」のオリジナルのサンプルでは、
alexa.APP_ID = APP_ID;
となっています。 "alexa.APP_ID"は"alexa.appId"の間違いでしょう。 javascriptだとこの手の間違いは文法上間違いではないので、なかなか顕在化しませんが、 TypeScriptだと翻訳段階(チェック機能のあるエディタだとコーディング段階)であっさりと検出できます。
なので、TypeScriptの方が開発効率も安全性も向上すると思うんだけどなあ。
おまけ: インテントオブジェクトへのアクセス
多くの場合、 インテントのハンドラでは、インテントの内容を見なければなりません。 例えば、スロットの値を取得したりします。
'MyIntent': function () { let value = this.event.request.intent.slots.MySlot.value; // スロットMySlotを参照 ...
これを単純にTypeScriptで型付けすると、エラーになります。
'MyIntent': function (this: Alexa.Handler<Alexa.IntentRequest>): void { let value = this.event.request.intent.slots.MySlot.value; // スロットMySlotを参照 ...
[ts] オブジェクトは 'undefined' である可能性があります。
要するに、 "this.event.request"はAlexa.IntentRequestインターフェイス型なのですが、 その"intent"プロパティの定義は「Alexa.Intentインターフェイス型、またはundefined」となっており、 undefinedの場合"intent.slots"がエラーになる、と言っています。
この場合、このハンドラが呼び出されている時点でintentはundefinedでないだろうから、 以下のように書けばよいでしょう。 (intentが万一undefinedならば実行エラーとなる)
'MyIntent': function (this: Alexa.Handler<Alexa.IntentRequest>): void { const intent = this.event.request.intent as Alexa.Intent; let value = intent.slots.MySlot.value; // スロットMySlotを参照 ...
« Docker for Windowsでコンテナイメージの置き場所を変える | トップページ | それさえもおそらくは平穏なWindows 10 Mobile »
「覚え書き」カテゴリの記事
- AlexaのカスタムスキルをTypeScriptで書く(2018.02.12)
- Docker for Windowsでコンテナイメージの置き場所を変える(2018.02.01)
- AthlonマシンにWindows UpdateでMeltdown/Spectre対応をあてるとWindowsが起動しなくなる件(2018.01.09)
- Docker for Windowsをアップデート/再インストールできない件(2017.10.28)
- PCの時計が1時間進む問題が完全に解決した(2017.04.06)
「作業記録」カテゴリの記事
- AlexaのカスタムスキルをTypeScriptで書く(2018.02.12)
- Docker for Windowsをアップデート/再インストールできない件(2017.10.28)
- PCの時計が1時間進む問題が完全に解決した(2017.04.06)
- PCの時計が1時間進む問題が発生したので調べてみた(もう答は出た、と思ったけどなんか混迷している)(2017.04.04)
- 認証プロキシをなんとかするためにツールを作っている話(2017.01.26)
« Docker for Windowsでコンテナイメージの置き場所を変える | トップページ | それさえもおそらくは平穏なWindows 10 Mobile »
コメント