- 投稿日:2019-10-02T22:11:22+09:00
Alexa スキル内課金 第1回 スキル内課金の仕組みとスキル内商品レコード作成、読み込み
はじめに
日本でもスキル内課金が解禁されましたので、今回はAlexaのスキル内課金の作り方を説明します。
スキル内課金を試してみようとスキルをつくり、記事用にも作ろうとしていました。いったん途中で誤って投稿してしまいましたが、削除しての出し直し記事です。
今回は、スキル内課金の仕組みとスキル内課金レコードの作成と、その読み込み方法を説明します。今回実施する内容
今回は、スキル内課金の仕組みとスキル内課金レコードの作成と、その読み込み方法を説明します。
コンソールログに以下を表示させます。console.logの表示を整形したもの現在登録済みのスキル内商品: [ { "productId":"amzn1.adg.product.XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX", "referenceName":"課金商品", "type":"ENTITLEMENT", "name":"課金商品", "summary":"説明です。", "entitled":"NOT_ENTITLED", "entitlementReason":"NOT_PURCHASED", "purchasable":"PURCHASABLE", "activeEntitlementCount":0, "purchaseMode":"TEST" } ]環境
OS:Windows 10 JP
Alexaスキル言語:Node.js
Editor:Visual Studio Code
Alexa developer console参考
・Alexaスキルを使用した商品の販売
Alexzオフィシャルドキュメントの説明です。・AlexaサービスAPIの呼び出し
Node.jsでスキル内課金サービスのAPIへアクセスする説明です。用語
スキル内課金 (ISP)
スキルの中の課金の仕組みこと。
In-skill Purchasing前提条件
特になし
スキル内課金の概要
スキル内課金の導入にあたり、スキル内課金を実現するための仕組みの概念図を示します。
まずはじめに上記の図はオリジナルで作成したものであり、アマゾンの更新のものではありませんので、参考程度に考えてください。
スキル内課金の大本は、スキル内商品レコードにあります。
その内容は、
- 商品名
- 商品ID
- 商品の説明
- 商品価格
- 商品の購入可否
- 商品の購入情報
など多くの情報が、スキル内商品レコードに保存されます。
スキル内商品レコードは、Amazon developer consoleやASK CLIで作成できます。スキルは、このスキル内商品レコードの情報を使い、購入しているか判断し、
- 未購入ならば、レコメンド。
- 購入済みならば、スキル内商品の提供
をします。
Alexaは、スキル経由で購入や返金を依頼され、
- ユーザーとの購入・キャンセルなどの対話(スキルを介さない対話)
- 課金処理(Amazon課金サーバへの購入通知
- 返金処理
- スキルへの購入結果の通知
を行います。
ユーザーとの購入の対話は、スキルを介さないで実施されます。
このスキルを介さないが大きな特徴のひとつです。
従来であれば、Alexaの仕事は、ユーザーとの対話において、言語の認識して、それをIntentという形にしてスキルに渡すことや、スキルからのResponseを言語に変えてユーザーに会話の形で渡すのが主な仕事でした。
しかし、スキル内課金においては、スキルから購入処理を要求すると、その後はAlexaがユーザーと対話して、購入や購入取り消しなどの対話を行い、その結果をスキルに返すという動作になります。
もちろん、Alexaが商品のことを知っているわけではないため、スキル内商品レコードに基づいて処理を行います。課金の仕組み自体は、どんなスキル内商品でもプロセスは同じで、
- それを開発者が毎回書くのは面倒
- 同じ処理のほうがユーザーにとっては新設
- アマゾンでPrime会員には割引などアマゾンによる柔軟な対応ができる
など、開発者にとっても、アマゾンにとっても、ユーザーにとってもメリットがあるようにしているのかなと思います。
といってもよいことばかりではなく、このために開発者はいろんな制限の中でスキル開発をしないといけないというのがでてきており、例えば、「商品の金額については、スキル内では触れてはならない」などですが、色々とあって、慣れるまでは面倒そうです。スキル内課金の作成
スキル内商品レコードの作成
スキル内商品のレコードは、ASK CLIでも作成できるようですが、今回は、Alexa developer consoleから作成します。
スキル内商品レコードには、
種類 概要 買い切り型 1回購入すればずっと利用可能なタイプ。有効期限なし。 サブスクリプション型 一定期間利用可能なタイプ。 消費型 1回利用可能なタイプ。 があります。
今回は「買い切り型」のスキル内商品レコードを作成します。
- Alexa developer consoleで、スキル内課金を作成するスキルのビルド画面を開く。
左側目のメニューから「スキル内商品」を選択し、****を開く。
◆スキル内商品画面
スキル内商品画面では、
・ リンクした商品
・ リンク可能な商品
・ スキル内商品を作成ボタン
など(赤枠のもの)が表示されます。
これは開発者の全体のスキルで共通で利用されるスキル内商品が表示されます。ただしリンク済みの商品はでません。
リンクした商品は、このスキルに関連付けられたスキルが表示されます。
リンク可能な商品は、スキル内商品レコードを作成したもので、まだどのスキルにも関連付けされていないものが表示されます。スキル内商品に適当な名称(ここでは、課金商品)をつけ、「買い切り型」を選択し、「スキル内商品を作成」ボタンを押下する。
以下のような画面が表示される。以下に従って入力する。
・課金商品
ここは、上記で記載した名称「課金商品」にしたものが記載される。・商品ID
自動で割当らえる識別子。入力は不要。・参照名
上記で**記載した名称「課金商品」にしたものが記載される。・サポートされる言語の「新しい言語を追加」
次項で説明する。・価格設定と購入可能状況
どこで販売するかですが、まずは日本だと思いますので、「Amazon.co.jp」を選択し、価格を設定する。
買い切り型とサブスクリプション型の場合、99円~9,999円の範囲で設定可能。
消費型の場合、99円~999円の範囲で設定可能。・料金のカテゴリー
以下のカテゴリーから選択。
- 情報サービス
- 定期刊行物
- ソフトウェア
- ストリーミングオーディオ
- ストリーミングラジオ
- ビデオ
いまいち、よくわからない分類で、どう使われるのかわかりません。
・テスト手順
アマゾンの試験車むけに使い方の説明を記載する。サポートされる言語の「新しい言語を追加」ボタンを押下し、「日本語(日本)」を選択すると、以下の画面が表示される。以下に従って入力する。
・表示名
スキル内商品の商品名として利用する名称をつける。・説明
スキル内商品の説明を記載し、この情報は、Alexaがユーザーと対話するときに利用される。・詳細な説明
さらに詳細な説明を記載する。今のところ、Alexaがユーザーとの対話で使っているようには見えないが、おそらく使われることになると思われる。・このスキル内商品の小さいアイコン
・このスキル内商品の大きいアイコン
スキル内商品のアイコンですが、いまのところどこで使うのか不明。Web上で出るスキルアイコンとは別で、スキル内商品向けですが、どこで出てくるのかな。・購入プロンプトの説明
購入したときに利用される音声メッセージを記載する。・購入確認の説明
購入した時にスキルアプリで表示されるメッセージを記載する。ここで、設定した値は、スキルからアクセスすることができます。取得方法は別途記載します。
以下のような情報になります。inSkillProducts[ { "productId":"amzn1.adg.product.XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX", "referenceName":"課金商品", "type":"ENTITLEMENT", "name":"課金商品", "summary":"説明です。", "entitled":"NOT_ENTITLED", "entitlementReason":"NOT_PURCHASED", "purchasable":"PURCHASABLE", "activeEntitlementCount":0, "purchaseMode":"TEST" } ]
要素名 スキル内商品レコード上の名前 説明 productId 商品ID 自動で割り当てられる一意な値。 referenceName 参照名 参照となる名称。基本的にnameと同じにする。 name 表示名 スキル内で表示される商品名。 summary 説明 商品の説明。 entitled なし スキル内商品の権利を示す。ENTITLED、または、NOT_ENTITLED purchasable なし 購入可能かどうかを示す。PURCHASABLE、またはNOT_PURCHASABLE activeEntitlementCount なし 購入した個数。消費型で使用。 purchaseMode なし 試験中か運用中かを示す。TEST、まてゃLIVE すべて入力したら、「保存」する。
すべて入力できていると、スキルとリンクするか聞かれるため、リンクすると、「リンクした商品」に表示されるようになる。なお、記載内容は変更できるため、途中で間違えても大丈夫です。
おそらく、公開された後も自由に変更できるように思います。(再度更新する時に一緒に更新されると思います)Node.jsからスキル内課金レコードへのアクセス
Node.jsからスキル内レコードへは、以下のMonetizationServiceClient APIを通じてアクセスします。
スキル内商品レコードで作成したように、商品は、地域(locale)ごとに管理されるため、localeを設定したうえで、情報を取得します。
AlexaサービスAPIの呼び出しに記載されている内容をベースに記載します。index.jsの一部const LaunchRequestHandler = { canHandle(handlerInput) { return handlerInput.requestEnvelope.request.type === 'LaunchRequest'; }, handle(handlerInput) { const locale = handlerInput.requestEnvelope.request.locale; const ms = handlerInput.serviceClientFactory.getMonetizationServiceClient(); return ms.getInSkillProducts(locale).then((result) => { console.log(`現在登録済みのスキル内商品: ${JSON.stringify(result.inSkillProducts)}`); return handlerInput.responseBuilder .speak("スキル内課金へようこそ") .withSimpleCard("スキル内課金", "スキル内課金へようこそ") .getResponse(); }); }, };上記を記載したうえで、このスキルを起動すると、console出力にいかが表示されます。
現在登録済みのスキル内商品: [{"productId":"amzn1.adg.product.XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX","referenceName":"課金商品","type":"ENTITLEMENT","name":"課金商品","summary":"説明です。","entitled":"NOT_ENTITLED","entitlementReason":"NOT_PURCHASED","purchasable":"PURCHASABLE","activeEntitlementCount":0,"purchaseMode":"TEST"}]ちょっとわかりづらいため、整形して表示します。
console.logの表示を整形したもの現在登録済みのスキル内商品: [ { "productId":"amzn1.adg.product.XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX", "referenceName":"課金商品", "type":"ENTITLEMENT", "name":"課金商品", "summary":"説明です。", "entitled":"NOT_ENTITLED", "entitlementReason":"NOT_PURCHASED", "purchasable":"PURCHASABLE", "activeEntitlementCount":0, "purchaseMode":"TEST" } ]スキル内商品レコードの内容が取得できたことがわかります。
ソースコードについて、少し説明します。
const locale = handlerInput.requestEnvelope.request.locale;
は、地域を取得しています。今回だと、スキル内商品作成で日本を選択しましたので、jp-JP
になります。
const ms = handlerInput.serviceClientFactory.getMonetizationServiceClient();
これは、MonetizationServiceClientの定数を設定しています。
return ms.getInSkillProducts(locale).then((result) => {
ここで、地域が「jp-JP」のスキル内商品レコードをすべて取得して、resultに入れています。
console.log(
現在登録済みのスキル内商品: ${JSON.stringify(result.inSkillProducts)});
その内容を、consoleに出力しただけです。今回はレコードが一つしか作成していないため、上記のようになりました。スキル内商品レコードについては、上のほうで述べましたので特筆することはありませんが、作成したばかりの状態では、以下のように、未購入(NOT_PURCHASED)であり資格はなく(NOT_ENTITLED)、個数は0で、購入可能(PURCHASABLE)だということです。
また、スキル内課金の状態が、TEST状態ということもわかります。"entitled":"NOT_ENTITLED", "entitlementReason":"NOT_PURCHASED", "purchasable":"PURCHASABLE", "activeEntitlementCount":0, "purchaseMode":"TEST"おわりに
今回は、以下を実施しました。
- スキル内課金の概要説明
- スキル内商品レコードの作成
- スキル内商品レコードのNode.jsからのアクセス
次回以降は、Alexaによるユーザーとのスキル内商品購入のやり方を説明やスキル内商品レコードの使い方について説明していきたいと思います。
- 投稿日:2019-10-02T22:11:22+09:00
Alexa スキル内課金 第1回 スキル内課金の仕組みとスキル内課金レコード作成、読み込み
はじめに
日本でもスキル内課金が解禁されましたので、今回はAlexaのスキル内課金の作り方を説明します。
スキル内課金を試してみようとスキルをつくり、記事用にも作ろうとしていました。いったん途中で誤って投稿してしまいましたが、削除しての出し直し記事です。
今回は、スキル内課金の仕組みとスキル内課金レコードの作成と、その読み込み方法を説明します。今回実施する内容
今回は、スキル内課金の仕組みとスキル内課金レコードの作成と、その読み込み方法を説明します。
コンソールログに以下を表示させます。console.logの表示を整形したもの現在登録済みのスキル内商品: [ { "productId":"amzn1.adg.product.XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX", "referenceName":"課金商品", "type":"ENTITLEMENT", "name":"課金商品", "summary":"説明です。", "entitled":"NOT_ENTITLED", "entitlementReason":"NOT_PURCHASED", "purchasable":"PURCHASABLE", "activeEntitlementCount":0, "purchaseMode":"TEST" } ]環境
OS:Windows 10 JP
Alexaスキル言語:Node.js
Editor:Visual Studio Code
Alexa developer console参考
・Alexaスキルを使用した商品の販売
Alexzオフィシャルドキュメントの説明です。・AlexaサービスAPIの呼び出し
Node.jsでスキル内課金サービスのAPIへアクセスする説明です。用語
スキル内課金 (ISP)
スキルの中の課金の仕組みこと。
In Skill Product前提条件
特になし
スキル内課金の概要
スキル内課金の導入にあたり、スキル内課金を実現するための仕組みの概念図を示します。
まずはじめに上記の図はオリジナルで作成したものであり、アマゾンの更新のものではありませんので、参考程度に考えてください。
スキル内課金の大本は、スキル内商品レコードにあります。
その内容は、
- 商品名
- 商品ID
- 商品の説明
- 商品価格
- 商品の購入可否
- 商品の購入情報
など多くの情報が、スキル内商品レコードに保存されます。
スキル内商品レコードは、Amazon developer consoleやASK CLIで作成できます。スキルは、このスキル内商品レコードの情報を使い、購入しているか判断し、
- 未購入ならば、レコメンド。
- 購入済みならば、スキル内商品の提供
をします。
Alexaは、スキル経由で購入や返金を依頼され、
- ユーザーとの購入・キャンセルなどの対話(スキルを介さない対話)
- 課金処理(Amazon課金サーバへの購入通知
- 返金処理
- スキルへの購入結果の通知
を行います。
ユーザーとの購入の対話は、スキルを介さないで実施されます。
このスキルを介さないが大きな特徴のひとつです。
従来であれば、Alexaの仕事は、ユーザーとの対話において、言語の認識して、それをIntentという形にしてスキルに渡すことや、スキルからのResponseを言語に変えてユーザーに会話の形で渡すのが主な仕事でした。
しかし、スキル内課金においては、スキルから購入処理を要求すると、その後はAlexaがユーザーと対話して、購入や購入取り消しなどの対話を行い、その結果をスキルに返すという動作になります。
もちろん、Alexaが商品のことを知っているわけではないため、スキル内商品レコードに基づいて処理を行います。課金の仕組み自体は、どんなスキル内商品でもプロセスは同じで、
- それを開発者が毎回書くのは面倒
- 同じ処理のほうがユーザーにとっては新設
- アマゾンでPrime会員には割引などアマゾンによる柔軟な対応ができる
など、開発者にとっても、アマゾンにとっても、ユーザーにとってもメリットがあるようにしているのかなと思います。
といってもよいことばかりではなく、このために開発者はいろんな制限の中でスキル開発をしないといけないというのがでてきており、例えば、「商品の金額については、スキル内では触れてはならない」などですが、色々とあって、慣れるまでは面倒そうです。スキル内課金の作成
スキル内商品レコードの作成
スキル内商品のレコードは、ASK CLIでも作成できるようですが、今回は、Alexa developer consoleから作成します。
スキル内商品レコードには、
種類 概要 買い切り型 1回購入すればずっと利用可能なタイプ。有効期限なし。 サブスクリプション型 一定期間利用可能なタイプ。 消費型 1回利用可能なタイプ。 があります。
今回は「買い切り型」のスキル内商品レコードを作成します。
- Alexa developer consoleで、スキル内課金を作成するスキルのビルド画面を開く。
左側目のメニューから「スキル内商品」を選択し、****を開く。
◆スキル内商品画面
スキル内商品画面では、
・ リンクした商品
・ リンク可能な商品
・ スキル内商品を作成ボタン
など(赤枠のもの)が表示されます。
これは開発者の全体のスキルで共通で利用されるスキル内商品が表示されます。ただしリンク済みの商品はでません。
リンクした商品は、このスキルに関連付けられたスキルが表示されます。
リンク可能な商品は、スキル内商品レコードを作成したもので、まだどのスキルにも関連付けされていないものが表示されます。スキル内商品に適当な名称(ここでは、課金商品)をつけ、「買い切り型」を選択し、「スキル内商品を作成」ボタンを押下する。
以下のような画面が表示される。以下に従って入力する。
・課金商品
ここは、上記で記載した名称「課金商品」にしたものが記載される。・商品ID
自動で割当らえる識別子。入力は不要。・参照名
上記で**記載した名称「課金商品」にしたものが記載される。・サポートされる言語の「新しい言語を追加」
次項で説明する。・価格設定と購入可能状況
どこで販売するかですが、まずは日本だと思いますので、「Amazon.co.jp」を選択し、価格を設定する。
買い切り型とサブスクリプション型の場合、99円~9,999円の範囲で設定可能。
消費型の場合、99円~999円の範囲で設定可能。・料金のカテゴリー
以下のカテゴリーから選択。
- 情報サービス
- 定期刊行物
- ソフトウェア
- ストリーミングオーディオ
- ストリーミングラジオ
- ビデオ
いまいち、よくわからない分類で、どう使われるのかわかりません。
・テスト手順
アマゾンの試験車むけに使い方の説明を記載する。サポートされる言語の「新しい言語を追加」ボタンを押下し、「日本語(日本)」を選択すると、以下の画面が表示される。以下に従って入力する。
・表示名
スキル内商品の商品名として利用する名称をつける。・説明
スキル内商品の説明を記載し、この情報は、Alexaがユーザーと対話するときに利用される。・詳細な説明
さらに詳細な説明を記載する。今のところ、Alexaがユーザーとの対話で使っているようには見えないが、おそらく使われることになると思われる。・このスキル内商品の小さいアイコン
・このスキル内商品の大きいアイコン
スキル内商品のアイコンですが、いまのところどこで使うのか不明。Web上で出るスキルアイコンとは別で、スキル内商品向けですが、どこで出てくるのかな。・購入プロンプトの説明
購入したときに利用される音声メッセージを記載する。・購入確認の説明
購入した時にスキルアプリで表示されるメッセージを記載する。ここで、設定した値は、スキルからアクセスすることができます。取得方法は別途記載します。
以下のような情報になります。inSkillProducts[ { "productId":"amzn1.adg.product.XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX", "referenceName":"課金商品", "type":"ENTITLEMENT", "name":"課金商品", "summary":"説明です。", "entitled":"NOT_ENTITLED", "entitlementReason":"NOT_PURCHASED", "purchasable":"PURCHASABLE", "activeEntitlementCount":0, "purchaseMode":"TEST" } ]
要素名 スキル内商品レコード上の名前 説明 productId 商品ID 自動で割り当てられる一意な値。 referenceName 参照名 参照となる名称。基本的にnameと同じにする。 name 表示名 スキル内で表示される商品名。 summary 説明 商品の説明。 entitled なし スキル内商品の権利を示す。ENTITLED、または、NOT_ENTITLED purchasable なし 購入可能かどうかを示す。PURCHASABLE、またはNOT_PURCHASABLE activeEntitlementCount なし 購入した個数。消費型で使用。 purchaseMode なし 試験中か運用中かを示す。TEST、まてゃLIVE すべて入力したら、「保存」する。
すべて入力できていると、スキルとリンクするか聞かれるため、リンクすると、「リンクした商品」に表示されるようになる。なお、記載内容は変更できるため、途中で間違えても大丈夫です。
おそらく、公開された後も自由に変更できるように思います。(再度更新する時に一緒に更新されると思います)Node.jsからスキル内課金レコードへのアクセス
Node.jsからスキル内レコードへは、以下のMonetizationServiceClient APIを通じてアクセスします。
スキル内商品レコードで作成したように、商品は、地域(locale)ごとに管理されるため、localeを設定したうえで、情報を取得します。
AlexaサービスAPIの呼び出しに記載されている内容をベースに記載します。index.jsの一部const LaunchRequestHandler = { canHandle(handlerInput) { return handlerInput.requestEnvelope.request.type === 'LaunchRequest'; }, handle(handlerInput) { const locale = handlerInput.requestEnvelope.request.locale; const ms = handlerInput.serviceClientFactory.getMonetizationServiceClient(); return ms.getInSkillProducts(locale).then((result) => { console.log(`現在登録済みのスキル内商品: ${JSON.stringify(result.inSkillProducts)}`); return handlerInput.responseBuilder .speak("スキル内課金へようこそ") .withSimpleCard("スキル内課金", "スキル内課金へようこそ") .getResponse(); }); }, };上記を記載したうえで、このスキルを起動すると、console出力にいかが表示されます。
現在登録済みのスキル内商品: [{"productId":"amzn1.adg.product.XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX","referenceName":"課金商品","type":"ENTITLEMENT","name":"課金商品","summary":"説明です。","entitled":"NOT_ENTITLED","entitlementReason":"NOT_PURCHASED","purchasable":"PURCHASABLE","activeEntitlementCount":0,"purchaseMode":"TEST"}]ちょっとわかりづらいため、整形して表示します。
console.logの表示を整形したもの現在登録済みのスキル内商品: [ { "productId":"amzn1.adg.product.XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX", "referenceName":"課金商品", "type":"ENTITLEMENT", "name":"課金商品", "summary":"説明です。", "entitled":"NOT_ENTITLED", "entitlementReason":"NOT_PURCHASED", "purchasable":"PURCHASABLE", "activeEntitlementCount":0, "purchaseMode":"TEST" } ]スキル内商品レコードの内容が取得できたことがわかります。
ソースコードについて、少し説明します。
const locale = handlerInput.requestEnvelope.request.locale;
は、地域を取得しています。今回だと、スキル内商品作成で日本を選択しましたので、jp-JP
になります。
const ms = handlerInput.serviceClientFactory.getMonetizationServiceClient();
これは、MonetizationServiceClientの定数を設定しています。
return ms.getInSkillProducts(locale).then((result) => {
ここで、地域が「jp-JP」のスキル内商品レコードをすべて取得して、resultに入れています。
console.log(
現在登録済みのスキル内商品: ${JSON.stringify(result.inSkillProducts)});
その内容を、consoleに出力しただけです。今回はレコードが一つしか作成していないため、上記のようになりました。スキル内商品レコードについては、上のほうで述べましたので特筆することはありませんが、作成したばかりの状態では、以下のように、未購入(NOT_PURCHASED)であり資格はなく(NOT_ENTITLED)、個数は0で、購入可能(PURCHASABLE)だということです。
また、スキル内課金の状態が、TEST状態ということもわかります。"entitled":"NOT_ENTITLED", "entitlementReason":"NOT_PURCHASED", "purchasable":"PURCHASABLE", "activeEntitlementCount":0, "purchaseMode":"TEST"おわりに
今回は、以下を実施しました。
- スキル内課金の概要説明
- スキル内商品レコードの作成
- スキル内商品レコードのNode.jsからのアクセス
次回以降は、Alexaによるユーザーとのスキル内商品購入のやり方を説明やスキル内商品レコードの使い方について説明していきたいと思います。
- 投稿日:2019-10-02T19:58:42+09:00
AWSのAPIでスロットリングされたらリトライするようにする
e.code === 'Throttling'
でエラーがthrowされるので、これを拾ってリトライするように実装すればとりあえずOK。
ただし再帰呼び出しになるので、リトライ数を記録して無限ループにならないようにする必要はある。// Wait処理 const sleep = async (time = 1000) => { return new Promise(resolve => setTimeout(resolve, time)) } // CloudFrontのupdate処理 const worker = async (targetID, retryCount = 0) => { try { const dist = await cloudfront.getDistribution({ Id: targetID }).promise() const distributionConfig = dist.Distribution.DistributionConfig distributionConfig.Enabled = false await cloudfront.updateDistribution({ Id: dist.Distribution.Id, IfMatch: dist.ETag, DistributionConfig: distributionConfig }).promise() } catch (e) { if (e.code === 'Throttling') { if (retryCount < 2) { await sleep(2000) return worker(targetID, retryCount + 1) } } throw e } } const distIds = ['a', 'b', 'c'] Promise.all(distIds.map(async id => worker(id))
- 投稿日:2019-10-02T19:41:44+09:00
A3RTのTalk APIで返事をしてくれるDiscord Botを作ってみた
簡単なDiscord Botの作り方(初心者向け)
自動応答のDiscord Botを完全無料で構築する[gas][glitch]これらの記事を参考にしながら、A3RTのTalk APIを使ったDiscordのbotを作ってみました。
Botにリプライをすると、返事をしてくれます。以下のコードはBotの返信部分の実装です。
const discord = require("discord.js"); const client = new discord.Client(); client.on("message", message => { //メッセージが他のユーザーによるものだったら反応する if (message.isMemberMentioned(client.user)) { //先頭のユーザーIDを削除する var talkMassage = message.content.slice(22); const fetch = require('node-fetch'); const params = new URLSearchParams(); params.append('apikey', "APIキー"); params.append('query', talkMassage); fetch("https://api.a3rt.recruit-tech.co.jp/talk/v1/smalltalk", { method: 'POST', body: params } ) .then(Response => { Response.json().then(Data => { message .reply(Data.results[0].reply); }); }); } });何かありましたら気軽にコメントをください。
- 投稿日:2019-10-02T14:26:59+09:00
プロジェクトによって使うのnodeのバージョンが勝手に変わってほしい
前提
nodenv がインストールされていること。anyenv から入れてたら Node.js のバージョン管理はこれになってるはず。
方法
nodenv-package-json-engine をインストールするだけ。
git clone https://github.com/nodenv/nodenv-package-json-engine.git $(nodenv root)/plugins/nodenv-packageこれで package.json に
"engines": { "node": "^8.0.0" }あるいは
.node-version
とかがあれば勝手にノードのバージョンが変わってくれる。
- 投稿日:2019-10-02T14:26:59+09:00
プロジェクトによって使うnodeのバージョンが勝手に変わってほしい
前提
nodenv がインストールされていること。anyenv から入れてたら Node.js のバージョン管理はこれになってるはず。
方法
nodenv-package-json-engine をインストールするだけ。
git clone https://github.com/nodenv/nodenv-package-json-engine.git $(nodenv root)/plugins/nodenv-packageこれで package.json に
"engines": { "node": "^8.0.0" }あるいは
.node-version
とかがあれば勝手にノードのバージョンが変わってくれる。engines
はちゃんと書こうと思った。
- 投稿日:2019-10-02T10:58:56+09:00
Node.js学んでみた ~その② ルーティング編~
https://www.nodebeginner.org/index-jp.html#javascript-and-nodejs
↑node.jsについてすごくわかりやすく書かれた入門書?です。これを元に学習!【前回】
Node.js学んでみた ~その① 簡易サーバー作成編~猫でもわかる〜、猿でもわかる〜、って発祥はなんなんでしょうね?
私は昔ゲームを作りたくて「猫でもわかるC言語」というサイトを16年前ぐらいに見た記憶があるので、もうその頃からはあったはずです。一行も理解できなかった猫以下の頃でした。(小学校2、3年生)本題。
モジュールを作ってみる
前回学んだサーバーを建てるコードを、別モジュールに写します。
server.jsvar http = require('http'); function start(){ var html = require('fs').readFileSync('src/index.html') var server = http.createServer(function(request,response){ response.writeHead(200,{'Content-Type':'text/html;charset=utf-8'}); process.on('uncaughtException', (e) => console.log(e)); response.end(html); }) server.listen(8080,'127.0.0.1'); console.log("Server has started"); } exports.start = start;app.jsvar server = require("./server"); server.start();出したいコードをファンクション化して、
export.xxx
で登録!
あとは呼び出したいファイルでrequire
すればいいですね。
ターミナルで、
node app.js
とすればhttp://localhost:8080/ にちゃんと繋がります。ルーティングをやる
ルーティングっていうのはHTTPリクエストに応じてコードの表示先を変えることです。これができればグッとできることが増えそうですね。
使う道具(モジュール)たち!
url
・・・URLの各部分(リクエストパスとか)を抽出するメソッドを提供
querystring
・・・リクエストパラメータのクエリ文字をパースできるメソッドを提供HTTPリクエストをこれらを使ってやっつけます。
var http = require('http'); var url = require('url'); //urlモジュール追加 function start(){ var html = require('fs').readFileSync('src/index.html') var server = http.createServer(function(request,response){ var pathname = url.parse(request.url).pathname; console.log("Request for" + pathname + " received."); //パス表示 response.writeHead(200,{'Content-Type':'text/html;charset=utf-8'}); process.on('uncaughtException', (e) => console.log(e)); response.end(html); }) server.listen(8080,'127.0.0.1'); console.log("Server has started"); } exports.start = start;url部分をパチパチ打ち込んで変えてみると、ちゃんとターミナルにそれが表示されるのがわかるかと思います。
これを使えばルーティングできますね。このままでは管理しずらいのでサーバーとルーターの機能を分けます。
router.jsfunction route(pathname) { console.log("About to route a request for " + pathname); } exports.route = route;パス表示部分のファイルですね。
app.jsvar http = require('http'); var url = require('url'); function start(route){ var html = require('fs').readFileSync('src/index.html') var server = http.createServer(function(request,response){ var pathname = url.parse(request.url).pathname; route(pathname); //ルーティング部分 response.writeHead(200,{'Content-Type':'text/html;charset=utf-8'}); process.on('uncaughtException', (e) => console.log(e)); response.end(html); }) server.listen(8080,'127.0.0.1'); console.log("Server has started"); } exports.start = start;サーバー部分の処理になります。routeを呼び出していますね。
app.jsvar server = require("./server"); var router = require("./router"); server.start(router.route);2つのモジュールを呼び出して、関数を読み込んで実行します。
これで機能別にモジュール化できたので、あとはルーティングの肝部分です!
リクエストハンドラーは増減が激しいのでオブジェクトとして登録します。
まずはrequestHanders.jsというファイルを以下のように作ります。requestHanders.jsfunction start(){ console.log("startが呼ばれました"); } function upload(){ console.log("uploadが呼ばれました"); } exports.start = start; exports.upload = upload;お次にこれらをapp.jsで呼び出して、オブジェクトとして登録。
startに関しては、/
でもルーティングできるようになっていますね。
オブジェクトをsever.startに第二引数として渡しています。app.jsvar server = require("./server"); var router = require("./router"); var requestHandlers = require("./requestHandlers"); //呼び出し var handle = {} //オブジェクト化 handle["/"] = requestHandlers.start; handle["/start"] = requestHandlers.start; handle["/upload"] = requestHandlers.upload; server.start(router.route,handle);次はappから呼び出されているserver.jsを修正します。
function start
の第二引数handle
を増やしてあげて、
route
関数呼び出し時の第一引数にそのままhandle
を入れています。server.jsvar http = require('http'); var url = require('url'); function start(route,handle){ var html = require('fs').readFileSync('src/index.html') var server = http.createServer(function(request,response){ var pathname = url.parse(request.url).pathname; console.log("Request for" + pathname + " received."); route(handle,pathname); response.writeHead(200,{'Content-Type':'text/html;charset=utf-8'}); process.on('uncaughtException', (e) => console.log(e)); response.end(html); }) server.listen(8080,'127.0.0.1'); console.log("Server has started"); } exports.start = start;最後に
router.js
の修正です。router.jsfunction route(handle,pathname){ console.log("About to route a request for " + pathname); if(typeof handle[pathname]==='function'){ handle[pathname](); }else{ console.log("No request handler found for" + pathname); } } exports.route = route;
handle[pathname]
がfunctionである時、つまり存在する時は
handle[pathname]();
、つまりrequestHandlers.jsの関数たちが実行されます。
functionでない時、つまり存在しない時はエラーメッセージをconsole.log
で出してあげていますね。モジュールをたくさん分離していますが、おかげで各機能が明白でいい感じです。
Server has started Request for/ received. About to route a request for / startが呼ばれましたhttp://localhost:8080/ にアクセスすると上記のようになります。
Request for/upload received. About to route a request for /upload uploadが呼ばれましたhttp://localhost:8080/upload にアクセスすると上記のようになります。
ルーティング成功です!
(※faviconに関するメッセージは省略しています)
- 投稿日:2019-10-02T01:19:33+09:00
ConfluenceとJiraをnode.jsで作った簡易プロキシの後ろで動かす
JiraとConfluence、Node.js上で動作するWebアプリを同一PC上で動作させようとすると、CORSの問題でREST APIにアクセスできない。ローカルで動作確認するために動かすだけなら、Node.jsで簡単なプロキシサーバを作ることで対応することができるので、その設定方法をまとめる。(それ以前に、サーバ側の設定を変えればよいという話もあるが…本番環境に似せたいので)
設定したいこと
同一PC上でJiraとConfluenceが
localhost
の別々のポートで稼働しているときに、プロキシにより、同じポート(ここでは10000)以下の/jira
と/confluence
でアクセスできるようにする。なおConfluenceの同時編集で使われるSynchronyという機能については設定しなくても特に問題なかった。(プロキシを介さなくても良いから?)
アプリ 元々のURL Context Path変更後 プロキシ経由のURL Jira http://localhost:10080 http://localhost:10080/jira http://localhost:10000/jira Confluence http://localhost:8090 http://localhost:8090/confluence http://localhost:10000/confluence Confluenceの設定
AtlassianのConfluenceサポートに「NGINX を使用して Confluence へのリクエストをプロキシする方法」という記事に設定すべきことが載っている。なお、Jira/Confluence/Node.jsが同一PC上で動作し、PC外部からのリクエストが無ければ、「リダイレクション用URLの設定」はしない。
Context Pathの設定
<ConfluenceInstallDirectory>/conf/server.xml
を開き、Context
パスを設定する。server.xml変更前<Context path="" docBase="../confluence" debug="0" reloadable="false">デフォルトでは
""
になっているところに、/confluence
を入力することで、http://localhost:8090/confluence
でアクセスできるようになる。server.xml変更後<Context path="/confluence" docBase="../confluence" debug="0" reloadable="false">この設定をしないと表示がおかしくなる
この設定をせずにプロキシ経由
localhost:10000
でConfluenceにアクセスすると、正しく表示されない。これはConfluenceではCSSや各種リンクをベースURL(localhost:10000
)を基準にしてしまうため。Jiraの設定
Confluenceと同様、Context Pathの設定を行う。
Context Pathの設定
<JiraInstallDirectory>/conf/server.xml
を開き、Context
パスを設定する。server.xml変更前<Context path="" docBase="${catalina.home}/atlassian-jira" reloadable="false" useHttpOnly="true">デフォルトでは
""
になっているところに、/jira
を入力することで、http://localhost:10080/jira
でアクセスできるようになる。server.xml変更後<Context path="/jira" docBase="${catalina.home}/atlassian-jira" reloadable="false" useHttpOnly="true">簡単なプロキシを作る
localhost:10000
へのリクエストを、JiraとConfluenceに振り分けるプロキシは、以下の2つのパッケージのサンプルを真似て作れた。(エラーやセキュリティは考えていないが、とりあえず動く)index.jsconst http = require('http'); const httpProxy = require('http-proxy'); const HttpProxyRules = require('http-proxy-rules'); const port = 10000; // https://github.com/donasaur/http-proxy-rules const rules = new HttpProxyRules( { rules: { '/jira(/?.*)$' : 'http://localhost:10080/jira$1', '/confluence(/?.*)$' : 'http://localhost:8090/confluence$1' } } ); const proxy = httpProxy.createProxy(); proxy.on('error', function (err, req, res) { res.writeHead( 500, { 'Content-Type': 'text/plain' } ); res.end(err); }); http.createServer( ( req, res) => { // 対応するURLがあるか調べる const target = rules.match( req ); if( target ) { // マッチしたらリクエストを出す return proxy.web( req, res, { target: target } ); } // no match found res.writeHead( 500, { 'Content-Type': 'text/plain' } ); res.end( "Requested Url didn't match any of the listed rules."); } ).listen( port ); console.log( `Proxy is listening on port ${port}.`); process.on('SIGINT', () => { console.log( 'Proxy closed.' ); httpProxy.close(); });動作確認
node index.js
で簡易プロキシを起動し、localhost:10000/confluence
とlocalhost:10000/jira
にアクセスすると、ConfluenceとJiraの画面が表示されることが確認できた。