Twilio FunctionsとStudioを使って連続架電を行う

:::note alert
2023年5月1日を持ちまして、株式会社KDDIウェブコミュニケーションズのTwilioリセール事業が終了したため、本記事に記載されている内容は正確ではないことを予めご了承ください。
:::

# はじめに

Twilioを使うと連続架電が簡単にできます。
詳細は過去の[こちらの記事](https://qiita.com/mobilebiz/items/caf5a4d1889da4cc8dfd)を御覧ください。
今回は、Twilio FunctionsとStudioを使って、条件が満たされるまで連続で架電する仕組みを作ってみましょう。
シナリオとしては、サーバー障害などをトリガーとして、担当者が応答するまで複数の担当者に架電をしていくようなものを想定しています。
なお、留守番電話が応答してしまうことを想定して、相手が応答したら対応可能かを問い合わせるようにしています。

## 手順

今回の記事の手順は大きく次の5つになります。

1. 電話番号を購入します（この記事では割愛します）。
2. Flowを呼び出すFunctionsを作成します。
3. 架電するFlowを作成します。
4. 環境変数を設定します。
5. テスト

### 手順1. 電話番号を購入します

今回の記事を実行するためには、アップグレード済みのTwilioアカウントが必要になります。
詳しい手順については、以下の動画を参考にしてください。

- [トライアルアカウントの作成と電話番号の購入](https://www.youtube.com/watch?v=rNiaLjnhlB4)
- [アカウントのアップグレード方法](https://www.youtube.com/watch?v=MteO48Vudhg)

ここでは、050番号を一つ購入したものとして先に進めます。

### 手順2. Flowを呼び出すFunctionsを作成します

- 管理コンソールの左側のスライドメニューから**Runtime** > **Functions** > **Manage**を選択します。
- **Create a new Function**ボタンを押すか、赤いプラスアイコンを押してFunctionsを新規作成していきます。
- Function名は、「SerialCallStudio」とします。
- PATH名は、「/serial-call-studio」とします。PATH欄の右側にあるコピーアイコンを押して、このFunctionのPathをメモ帳などに記録しておきます。後ほどテストのときに使用します。
![SerialCallStudio_Function.png](https://qiita-image-store.s3.amazonaws.com/0/86046/d87f87bd-6ec4-f1c9-2d20-0d4e85cba4b2.png)

- **ACCESS CONTROL**のチェックボックスを外します。
- **CODE**欄に書かれているコードをすべて削除して、以下のコードを貼り付けます。架電先リストの内容をご自分の環境（かけたい相手の番号）に変更してください。なお、リストの登録数には制限はありませんので、必要に応じて増やしたり減らしたりしてください。

```javascript
/**
 *  Studioを連携させた連続架電 - serialCallStudio
 *
 *  @param idx  リストインデックス
 *  @param loop ループ回数
 */
// 架電先リスト（発信したい電話番号のリストをE.164形式で記述します）
const callList = {
  "携帯1": "+818012345678",
  "携帯2": "+819012345678",
  "携帯3": "+819011111111",
  "携帯4": "+819022222222",
};
// ループ回数（1以上の整数、1ならループしない）
const maxLoop = 2;

exports.handler = function(context, event, callback) {
  // カウンター関連
  let idx = event.idx || 0; // インデックスパラメータを取得
  let loop = event.loop || 1; // ループパラメータを取得
  if (idx >= Object.keys(callList).length) {  // リストの最後まで到達
    idx = 0;  // インデックスは0に戻す
    if (loop >= maxLoop) { // ループ回数が最大値を超えたので終了
      callback(null, "Call count was expired.");
    } else {
      loop++;  // ループ回数をインクリメント
    }
  }

  // 架電先電話番号
  let number = callList[Object.keys(callList)[idx]];
  idx++;  // インデックスをインクリメント

  // 架電するStudioフローを呼び出す
  const client = context.getTwilioClient();
  client.studio.v1.flows(context.FLOW_SID).executions.create({ 
    to: number, 
    from: context.FROM_NUMBER, 
    parameters: JSON.stringify({
      idx: idx,
      loop: loop,
    })
  })
  .then((call) => {
    callback(null, "OK");
  })
  .catch((error) => {
    callback(error);
  });
};
```

- **Save**ボタンを押して、設定を保存します。しばらくするとデプロイ完了のメッセージが表示されれば完了です。

### 手順3. 架電するFlowを作成します

- 管理コンソールの左側のスライドメニューから、**Studio**を選択します。
- 赤いプラスアイコンをクリックするか、「Create a new flow」を選択して、新しいフローを作成します。
- **FLOW NAME**に「SerialCallStudio」と入力して、**NEXT**ボタンを押します。
- キャンパスが表示されたら、**WIDGET LIBRARY**から**Make Outgoing Call**をキャンパス上にドラッグアンドドロップします。**Make Outgoing Call**は、Twilioから発信をするためのウィジェットです。
- **Trigger**ボックスの**REST API**と、今配置した「call_1」ボックスを結合します。
<img width="929" alt="スクリーンショット 2018-05-29 12.05.51.png" src="https://qiita-image-store.s3.amazonaws.com/0/86046/fd1d7149-da49-ba3f-5ade-4d9b9b0ec80d.png">

- **call_1**ウィジェットを選択して、**WIDGET NAME**を「Call」に変更します。これ以外の項目は変更する必要はありません。
- **SAVE**ボタンを押します。
- 続いて、**WIDGET LIBRARY**から**Gather Input on Call**をキャンバス上にドロップします。
- **Call**ウィジェットの**Answered**と、今ドロップした「gather_1」を結合します。**Answered**は、相手が応答したときのイベントとなります。
- **gather_1**ウィジェットを選択して、**WIDGET NAME**を「Gather」に変更します。
- **TEXT TO SAY**欄に応答した相手に伝えたいメッセージを記述します。今回は「異常が発生しました。対応は可能でしょうか？「はい」か、「いいえ」で応答してください。」と入力しておきます。
- **LANGUAGE**のプルダウンから「Japanese」を選択し、**MESSAGE VOICE**のプルダウンリストからお好きな声を選択します。
- **SPEECH RECOGNITION LANGUAGE**のプルダウンリストから「Japanese (Japan)」を選択します。似たような言語が他にもあるので、間違えないようにしてください。ここの選択を間違えると音声認識がうまく動作しません。
- **SPEECH RECOGNITION HINTS**欄には、「はい, いいえ」と入力しておきます。**SPEECH RECOGNITION HINTS**は、音声認識に関するヒントを指定する項目で、発話する内容が予め決まっているような場合は音声認識の精度が向上します。
<img width="308" alt="スクリーンショット 2018-11-29 12.38.40.png" src="https://qiita-image-store.s3.amazonaws.com/0/86046/805ab8c2-9a77-0b09-d8d5-971e059254e3.png">![スクリーンショット 2018-11-30 17.04.43.png](https://qiita-image-store.s3.amazonaws.com/0/86046/7aa5aca8-3c89-ddbf-9d1d-3c8ca78d1895.png)

- ここまで設定ができたら、**SAVE**ボタンを押して設定を保存します。

- 続いて、**WIDGET LIBRARY**から**Split Based On...**をキャンバス上にドロップします。**Split Based On...**ウィジェットは、条件分岐を設定するためのものです。今回は、直前のGatherウィジェットで発話された内容（「はい」か「いいえ」）を判定するために使用します。
- **Gather**ウィジェットの**User Said Something**と、今ドロップした**split_1**ウィジェットを結合します。
- **split_1**ウィジェットを選択して、**WIDGET NAME**を「YesOrNo」に変更します。
- **VARIABLE TO TEST**欄をクリックして、表示されたリストの中から**Gather**の**SpeechResult**を選択します。
<img width="305" alt="スクリーンショット 2018-11-29 12.47.48.png" src="https://qiita-image-store.s3.amazonaws.com/0/86046/cc9e95a8-e766-43bb-0e5a-417ab391b01c.png">

- 続いて、同じウェジェットの**Transitions**タブに移動し、**NEW CONDITION**の右側に表示されている赤いプラスアイコンを押します。
<img width="309" alt="スクリーンショット 2018-11-29 12.51.02.png" src="https://qiita-image-store.s3.amazonaws.com/0/86046/0c2bcdf2-aa86-1c06-2e11-b631798a6be5.png">

- 比較条件のプルダウンリストは「Equal to」、比較対象欄に「はい」と入力して**Set condition**と書かれた赤いリンクをクリックして設定を保存します。
<img width="283" alt="スクリーンショット 2018-11-29 14.15.41.png" src="https://qiita-image-store.s3.amazonaws.com/0/86046/65d25ae2-bb04-04e3-035a-b253cc0f63b5.png">

- ウィジェットのプロパティページを上にスクロールして、先ほどと同じく**NEW CONDITION**の右側に表示されている赤いプラスアイコンを押します。
- 比較条件のプルダウンリストは「Equal to」、比較対象欄に「いいえ」と入力して**Set condition**と書かれた赤いリンクをクリックして設定を保存します。この作業により**YesOrNo**ウィジェットに「はい」と「いいえ」の接続ポイントが追加されたことがわかります。
<img width="620" alt="スクリーンショット 2018-11-29 14.21.30.png" src="https://qiita-image-store.s3.amazonaws.com/0/86046/d99ac290-8e4a-4724-f3be-0e6aeb5cf0a5.png">

- 続いて、**WIDGET LIBRARY**から**Say/Play**をキャンバス上にドロップします。
- **YesOrNo**ウィジェットの**If value equal_to はい**と、今ドロップした**say_play_1**ウィジェットを結合します。
- **say_play_1**ウィジェットを選択して、**WIDGET NAME**を「SayYes」に変更します。
- **TEXT TO SAY**欄に、「ありがとうございます。よろしくお願いいたします。」と入力します。
- **LANGUAGE**のプルダウンリストから「Japanese」を選択し、**MESSAGE VOICE**はお好きな音声を選択します。
- **Save**ボタンを押して、設定を保存します。
- 続いて、**WIDGET LIBRARY**から**Run Function**をキャンバス上にドロップします。
- **YesOrNo**ウィジェットの**If value equal_to いいえ**と、今ドロップした**run_function_1**ウィジェットを結合します。
- **run_function_1**ウィジェットを選択して、**WIDGET NAME**を「CallNext」に変更します。
- **FUNCTION URL**のプルダウンリストから、先程作成した「SerialCallStudio」を選択します。
- **Function Parameters**の右側にある**Add**をクリックし、以下の２つの値を設定します。

|KEY|VALUE|
|:--|:--|
|idx|{{flow.data.idx}}|
|loop|{{flow.data.loop}}|

![スクリーンショット 2018-11-30 17.20.08.png](https://qiita-image-store.s3.amazonaws.com/0/86046/6aafe0ef-bc3a-3d40-c6b2-f55bfd679262.png)

- **Save**ボタンを押して、設定を保存します。

- **YesOrNo**ウィジェットの**No Condition Matches**と、**Gather**ウィジェットも結合します。これにより、「はい」や「いいえ」以外を回答されたときに、再度応答を促すことができます。
- **Gather**ウィジェットの**No Input**と、**CallNext**ウィジェットを結合します。
- **Call**ウィジェットの**Busy**と、**No Answer**、**Call Failed**を、すべて**CallNext**ウィジェットに結合します。これにより、架電先が電話に出ない場合や話し中の場合には、次の人に架電されるようになります。

ここまで作成すると、次のようなフローが出来上がります。
![スクリーンショット 2018-11-30 17.29.45.png](https://qiita-image-store.s3.amazonaws.com/0/86046/b97c916d-7ec7-3641-0aaa-2e76a325b2ec.png)


- 最後に画面上部にある赤い**Publish**のボタンを押します。
- 確認のダイアログが表示されるので、**Publish**ボタンを押します。

![スクリーンショット 2018-07-26 10.28.37.png](https://qiita-image-store.s3.amazonaws.com/0/86046/35c09e91-901a-8614-4498-e004bb04f1f4.png)

### 手順4. 環境変数を設定します

- 管理コンソールのスライドメニューから**Runtime** > **Functions** > **Configure**を選択します。
- **Environment Variables**の赤いプラスアイコンを２回押して、以下の２つの変数を追加してください。

|Key|Value|
|:--|:--|
|FROM_NUMBER|購入した050番号(E.164形式）|
|FLOW_SID|先程作成したStudioフローのSID（FWから始める文字列）|

- **Save**ボタンを押して、設定を保存します。

### 手順5. テスト

- 手順2で作成したFunctionのPathをブラウザで開きます。
- Function内で指定した電話番号に対して電話がかかることを確認します。

## まとめ

実際の利用シーンでは、テストで使用したURLを起動するようにすればOKです。
ただし、このままではURLがバレてしまうと、他の人が電話をできるようになってしまいますので、以下の記事を参考にセキュリティ対策をされることをおすすめします。
[Twilio Functionを外部からセキュアに呼び出す](https://qiita.com/mobilebiz/items/f8a8c795d5187e67166a)

StudioをFunctionsから呼び出すことで、相手が応答した場合やしない場合などをわかりやすく管理することができます。
**CallNext**ウィジェットにわたす変数などを間違えてしまうと、思わぬループに入ってしまうこともありますので、実際に利用する場合には十分注意をしてください。なお、予期せぬループが発生してしまった場合は、Functionsを削除するか、Path名を一時的に変更するなどしてループを止めてください。