Edited at

Twilio x NCMBハンズオン


はじめに

本記事は、2019年5月27日に開催された「Twilio x NCMBハンズオン」のTwilioのパートで利用したものです。

実施にあたって、すでにTwilioのアカウントを取得していることとします。もしまだ取得していない場合は、こちらからオンラインサインアップしてください。


ハンズオン手順

今回のハンズオンでは、スマホからRestAPI経由でTwilio Studioを呼び出し、呼び出されたフローから電話を架けます。相手が応答するとメッセージを流して、なにかキー入力を行います。入力されたキーは、Twilio Syncを使ってアプリに通知します。

この流れを実装するために、このパートで実施する手順は以下のとおりです。


  1. Syncサービスの作成とAPIキー生成

  2. Token生成用Functionの生成

  3. 応答キーのSync書き込みFunctionの生成

  4. 電話番号の購入

  5. Twilio Studioのフロー作成


ハンズオン

それでは早速始めていきましょう。


Syncサービスの作成とAPIキー生成


Twilio Syncとは


  • Twilio Sync

  • 複数のデバイス間で情報を同期させるためのサービス

  • WebSocketやSocket.ioのようなリアルタイム通信プラットフォーム

  • Syncは、受け取ったデータをTwilio上に保存してから配信する

  • Sync上のデータは、TTLを設定するか、明示的に削除しない限り消えない

  • 月10,000アクションまでは無料、それ以降は1000アクション単位で1.5円

  • Documents / Lists / Maps / Message Streams の4種類のデータを扱うことが可能

まずは、新しいSyncサービスを作成し、API KeyとAPI Tokenを発行します。


  • 管理コンソールのスライドメニューから、DEVELOPER TOOLSの中のSyncを選択します。


  • Syncメニューの中からServicesを選択します。


  • 赤い+アイコンを押して、新しいSyncサービスを作成します。


  • 新規Sync Serviceの作成ダイアログが表示されるので、名前欄に「NCMB」と入力し、Createボタンを押します。



  • SERVICE SID(ISから始まる文字列)をメモ帳に保存しておきます。

  • Syncメニューの< Backをクリックして、Syncサービスの一覧画面に戻ります。

  • Syncメニューのツールを選択し、APIキーの一覧が表示されるのを確認します。


  • Create API Keyボタンを押すか、+アイコンを押して、新しいAPI Keyを作成します。

  • 名前欄に「NCMB」と入力し、キータイプは「Standard」を選択します。



  • APIキーを作成するボタンを押します。

  • 表示されるSID(SKから始まる文字列)と、SECRETに表示されている文字列の両方をメモ帳に保存します。


  • 完了しました!のチェックボックスにチェックを入れて、終了ボタンを押します。


Token生成用Functionの生成

上記手順でTwilio SyncのAPIキーを生成しましたが、実際にSyncを利用するためには、作成したAPIキーとSecretをつかって、Tokenを生成する必要があります。Tokenには有効期限があり、期限を超えるとTokenは使えなくなります。

Token自体は文字列で、TwilioにはTokenを作成するためのSDKが用意されています。今回は、Twilio Functionsというサーバーレス実行環境を使って、Tokenを動的に生成するためのプログラムを作ります。


  • 管理コンソールから、Runtime > Functions > 設定を選択します。


  • Enable ACCOUNT_SID and AUTH_TOKENのチェックボックスをONにします。


  • Environmental Variablesの赤い+ボタンを3回押して、KEY/VALUE欄を3つ追加します。

  • それぞれのKEY/VALUEを以下のように設定します。

KEY
VALUE

SYNC_SERVICE_SID
ISから始まるSyncサービスのSID

TWILIO_API_KEY
SKから始まるAPIキー

TWILIO_API_SECRET
APIキーとセットで設定されたSECRET



  • Saveボタンを押して設定を保存します。


  • RuntimeメニューのFunctions > Manageを選択します。


  • Create a Functionボタンを押すか、もしくは赤い+アイコンをクリックして、新しいFunctionを生成します。

  • テンプレートの一覧からBlankを選択して、Createボタンを押します。


  • FUNCTION NAMEに「SyncToken」と入力し、PATHには、「/sync-token」と入力します。



  • ACCESS CONTROLのチェックは外しておきます。


  • CODE欄に書かれているコードを以下のコードに置き換えます。

exports.handler = function(context, event, callback) {

const ACCOUNT_SID = context.ACCOUNT_SID;
const SERVICE_SID = context.SYNC_SERVICE_SID;
const API_KEY = context.TWILIO_API_KEY;
const API_SECRET = context.TWILIO_API_SECRET;
const IDENTITY = context.DOMAIN_NAME;
const AccessToken = Twilio.jwt.AccessToken;
const SyncGrant = AccessToken.SyncGrant;

const syncGrant = new SyncGrant({
serviceSid: SERVICE_SID
});

const accessToken = new AccessToken(
ACCOUNT_SID,
API_KEY,
API_SECRET
);

accessToken.addGrant(syncGrant);
accessToken.identity = IDENTITY;
if (event.callback) {
let response = new Twilio.Response();
response.setBody(`${event.callback}(${JSON.stringify({token: accessToken.toJwt()})})`);
response.appendHeader('Content-Type', 'application/javascript');
callback(null, response);
} else {
callback(null, {
token: accessToken.toJwt()
});
}
};



  • Saveボタンを押して、デプロイされるのを待ちます。

  • デプロイが完了したら、PATH欄のURLをコピーして、別のブラウザタブで実行してみます。

  • JSON形式の長い文字列が表示されたら、トークンの生成は成功です。


応答キーのSync書き込みFunctionの生成

次に、電話に応答したあとに押されたキーをSyncに書き込むFunctionを作りましょう。

Syncで共有できるデータには、Documents、Maps、Listsがありますが、今回の目的であれば単純なKey/Valueで良いかと思いますので、Documentsを使うことにします。


Documentsオブジェクトとは


  • 単一のJSON形式オブジェクト

  • 1データのサイズは最大16KB

  • 単純な値のPUB/SUBに向いている

  • 履歴を取るような使い方には向いていない

  • JavaScript SDKを使うと、Documentオブジェクトの生成、更新、購読、削除が可能

  • サーバーサイドのREST API経由でも管理可能

作成するDocumentの例(Keyが電話番号、押されたキー値がValue)

{

+819012345678:1
}

これから作成するFunctionは、Twilio Studioから呼ばれます。呼ばれたときに、パラメータとして電話番号と押されたキー値を受け取ります。パラメータの名前は以下の通り定義しておきます。

パラメータ名
内容

number
電話番号

digit
押されたキー値


  • 管理コンソールのRuntimeメニューのFunctions > Manageを選択します。

  • 赤い+アイコンをクリックして、新しいFunctionを生成します。

  • テンプレートの一覧からBlankを選択して、Createボタンを押します。


  • FUNCTION NAMEに「SetDigit」と入力し、PATHには、「/set-digit」と入力します。


  • ACCESS CONTROLのチェックは外しておきます。


  • CODE欄に書かれているコードを以下のコードに置き換えます。

exports.handler = function(context, event, callback) {

const number = event.number || '';
const digit = event.digit || '';
let sync = Runtime.getSync({serviceName: context.SYNC_SERVICE_SID});
let payload = {};
payload[number] = digit;

sync.documents("Digits")
.update({
data: payload
}).then(response => {
console.log(response)
callback(null, 'OK')
}).catch(error => {
sync.documents.create({
uniqueName: "Digits",
data: payload
}).then(response => {
console.log(response)
callback(null, 'OK')
}).catch(error => {
console.log(error)
callback(error)
})
})
};



  • Saveボタンを押して、デプロイされるのを待ちます。

  • デプロイが完了したら、PATH欄のURLをコピーして、メモ帳に保存しておきます。


電話番号の購入

では次に、電話をかけるための電話番号を購入します。

今回は050番号を購入します。

すでに電話番号を購入している人は、その番号を使うこともできます。

もしまだ購入していない人は、以下の手順で番号を購入します。


  • ブラウザでログイン画面を表示し、ご自分のIDとパスワードでログインをします。

  • 管理コンソールの左側にあるボタンアイコンを押すと、スライドメニューが表示されます。


  • スライドメニューの一覧から、電話番号を選択します。



- Phone Numbersメニューの中のBuy a Numberを選択します。

- 国のプルダウンから「Japan(+81)」を選択し、検索ボタンを押します。


  • 一覧表示されたリストの中から、TYPEがローカルになっている(108円)の番号を一つ選び、購入ボタンを押します。

  • この番号を購入しますか?というダイアログが出たら、この番号を購入する。を押します。

  • Congratulationのダイアログが表示されたら、購入完了です。閉じるボタンを押します。

  • Phone Numbersの中のManage Numbersを選択し、今購入した電話番号が表示されることを確認します。

  • 購入した電話番号はメモ帳にコピーしておいてください。


Twilio Studioのフロー作成

さて、いよいよ最後の作業となります。

Twilioを使って電話を架けて、相手が応答したらメッセージを流して、キー応答を受け付けます。受け付けたキー情報は、先ほど作成したFunctionに渡していきます。

意外と難しそうに見えますが、Twilio Studioを使うとドラッグアンドドロップでコールフローを作成できます。

今回はすでに作成済みのフローをインポートして再利用したいと思います。


  • 管理コンソールの左側のスライドメニューから、Studioを選択します。

  • 赤いプラスアイコンをクリックするか、Create a new flowを選択して、新しいフローを作成します。


  • FLOW NAMEに「CallPhone」と入力して、NEXTボタンを押します。


  • New Flowダイアログが表示されるので、Import from JSONを選択して、Nextボタンを押します。



  • New Flowダイアログが開きますので、以下のJSONを貼り付けます。

{

"description": "CallPhone",
"states": [
{
"type": "InitialState",
"name": "Trigger",
"properties": {
"offset": {
"x": 0,
"y": 0
},
"flow_url": "https://webhooks.twilio.com/v1/Accounts/ACxxxx/Flows/FWxxxx"
},
"transitions": [
{
"event": "incomingMessage",
"conditions": [],
"next": null,
"uuid": "28d3857d-4e53-48d1-9a3b-c91fc8ee3ff9"
},
{
"event": "incomingCall",
"conditions": [],
"next": null,
"uuid": "2d918680-d0bb-4e24-9565-40e45b21990b"
},
{
"event": "incomingRequest",
"conditions": [],
"next": "FFde9b38ef0d899aef5b9137de7f9345b1",
"uuid": "5f50d2ee-7b21-47f4-acb9-f5e4027de178"
}
],
"sid": "FFe9513435797fbd0ed4d6ca2c40b0d900"
},
{
"sid": "FFde9b38ef0d899aef5b9137de7f9345b1",
"name": "CallPhone",
"type": "Dial",
"properties": {
"from": "{{flow.channel.address}}",
"to": "{{contact.channel.address}}",
"timeout": 60,
"offset": {
"x": 26.444444444444457,
"y": 219.44444444444446
}
},
"transitions": [
{
"widgetId": "FFde9b38ef0d899aef5b9137de7f9345b1",
"uuid": "b5cf69e9-750c-4481-a051-81de390c9c3a",
"event": "answered",
"conditions": [],
"next": "FF48e69d9c562d7965f647c47339359c8a"
},
{
"widgetId": "FFde9b38ef0d899aef5b9137de7f9345b1",
"uuid": "dfbe008b-6dc0-4b9c-9f2c-1163967d5bfe",
"event": "answeredByMachine",
"conditions": [],
"next": null
},
{
"widgetId": "FFde9b38ef0d899aef5b9137de7f9345b1",
"uuid": "b515fd2d-b2fd-44a2-8efa-9095c4135cab",
"event": "busy",
"conditions": [],
"next": null
},
{
"widgetId": "FFde9b38ef0d899aef5b9137de7f9345b1",
"uuid": "4328e132-0cc5-4bb7-87a8-fb0317c7b071",
"event": "noAnswer",
"conditions": [],
"next": null
},
{
"widgetId": "FFde9b38ef0d899aef5b9137de7f9345b1",
"uuid": "607880f6-3ee7-4c4f-8668-131abf284f15",
"event": "failed",
"conditions": [],
"next": null
}
]
},
{
"sid": "FF48e69d9c562d7965f647c47339359c8a",
"name": "GatherDigit",
"type": "Gather",
"properties": {
"loop": "3",
"timeout": 5,
"stop_gather": true,
"finish_on_key": "#",
"offset": {
"x": -90,
"y": 480
},
"say": "おてすうですが、いずれかの数字キーを押してください。",
"language": "ja-JP",
"voice": "Polly.Mizuki",
"gather_language": "en",
"play": null,
"number_of_digits": "1"
},
"transitions": [
{
"widgetId": "FF48e69d9c562d7965f647c47339359c8a",
"uuid": "fc2daf3d-ed8b-488a-9d4a-495aa810f6b5",
"event": "keypress",
"conditions": [],
"next": "FF8051dc505207a50adec485f061b56cbe"
},
{
"widgetId": "FF48e69d9c562d7965f647c47339359c8a",
"uuid": "9c2666ef-e7df-41f5-bf85-2d192320f34e",
"event": "speech",
"conditions": [],
"next": null
},
{
"widgetId": "FF48e69d9c562d7965f647c47339359c8a",
"uuid": "8068370c-0b32-4119-9a28-780eb2667370",
"event": "timeout",
"conditions": [],
"next": "FF48e69d9c562d7965f647c47339359c8a"
}
]
},
{
"sid": "FF8051dc505207a50adec485f061b56cbe",
"name": "SetDigit",
"type": "Function",
"properties": {
"offset": {
"x": -80,
"y": 730
},
"url": "https://xxxx-xxxx-xxxx.twil.io/set-digit",
"parameters": [
{
"key": "number",
"value": "{{widgets.CallPhone.To}}"
},
{
"key": "digit",
"value": "{{widgets.GatherDigit.Digits}}"
}
]
},
"transitions": [
{
"widgetId": "FF8051dc505207a50adec485f061b56cbe",
"uuid": "6c2d078a-d18e-42fb-b59f-3cbe078fba2b",
"event": "success",
"conditions": [],
"next": null
},
{
"widgetId": "FF8051dc505207a50adec485f061b56cbe",
"uuid": "2dfcc350-a4ee-41c9-b885-3960d1444100",
"event": "fail",
"conditions": [],
"next": null
}
]
}
]
}



  • Nextボタンを押します。

  • 次のようなフローが読み込まれます。



  • setDigitウィジェットを選択します。


  • FUNCTION URLのプルダウンリストから先程作成した「SetDigit」を選びます。


  • Saveボタンを押します。

  • 画面上部のPublishボタンを押します。

<重要>

もし値を変更したときは、Saveボタンで変更を保存した後、かならず画面上部の赤いPublishボタンを押してください。これを忘れると設定は反映しません。

このStudioフローをRestAPIで呼ぶためには以下の条件が必要です。


  • StudioフローのTriggerウィジェットを選択すると、RestAPI用のURLが表示されます。

  • POSTメソッドで呼び出してください。

  • Basic認証が必要です(UsernameとPasswordは、SyncのAPIキーとSecretがそのまま利用できます)。

  • 発信者番号と発信元番号は、それぞれToFromパラメータで指定が必要です。

  • Context-Typeは、application/x-www-form-urlencodedで送ってください。

Curlでの呼び出し例

curl -X POST \

https://studio.twilio.com/v1/Flows/FWxxxxxx/Executions \
-H 'Authorization: Basic ,Basic ' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'To=%2B8180XXXXXXXX&From=%2B8150XXXXXXXX'



Twilio(トゥイリオ)とは

https://twilio.kddi-web.com

Twilioは音声通話、メッセージング(SMS/チャット)、ビデオなどの 様々なコミュニケーション手段をアプリケーションやビジネスへ容易に組み込むことのできるクラウドAPIサービスです。初期費用不要な従量課金制で、各種開発言語に対応しているため、多くのハッカソンイベントやスタートアップなどにも、ご利用いただいております。