PHPでのTwilio自動音声の実装方法と日本語の読ませ方のコツ



##はじめに
株式会社じげんの前川です。
リフォームマッチングサイト　リショップナビの開発を担当しています。

今回は、phpで[Twilio](https://cloudapi.kddi-web.com/)を使った自動音声機能について書いていきます。
Twilioでは、取得した電話番号にwebhookを設定することで、APIを使って、音声を流したり、別の電話番号に転送をしたりすることができます。
今回は、電話がかかってきた際に、自動音声を流し、発信者の入力した郵便番号を取得するということを行いましたので、その実装方法と自動音声としてテキストを読み上げてもらうときに工夫した点について書いていきます。

##環境
・PHP7.2以上
・Twilio Sdk をインストール済み(twilio/sdk: 5.24)
※Laravel5系を使用しております

##前提
* Twilioのアカウントを所有
* （電話可能な）電話番号を取得済み

##目次
1. 概要
2. 実装　
3. Twilio側の設定
4. 最後に

##1.概要
下図の構成を実装します
![Twilio概要.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2286117/24f02af4-3ebf-4a13-bcd0-6e0a85b5e1e6.png)

Twilioの電話番号にユーザーが電話をかけると、図のincomingにリクエストが飛ぶようにします。（Twilioの管理画面で設定します。）
TwilioはTwimlというXMLを返すと、特定のアクション（決まった桁の番号を入力、特定のキーを押した、通話が切れたなど）をした場合に、再度Twilioからリクエストを返してくれます。
こちらを利用して、実装をしていきます。


##2.実装
概要でも触れた通り、基本的には、リクエストが来た際に[Twiml](https://jp.twilio.com/docs/voice/twiml)（Twilio用のタグが定義されたXML）を返す形で実装していきます。
Twilioから受け取るリクエストの詳細は[こちら](https://jp.twilio.com/docs/voice/twiml#request-parameters)を参考にしてください。
####incomingの実装
incomingでは「○○にお問合せの方は・・・」が流れた後に1　or 2を入力したらzipcodeに、#を入力 or 電話を切った場合にはendにリクエストを送るようなTwimlを返します。

```php
use Twilio\Twiml;

public function incoming(Request $request){

        $zipcodeUrl = zipcodeのURL;
        $endUrl =　endのURL;

        $response = new Twiml();
        $gather = $response->gather(array('numDigits' => 1, 'action' => $zipcodeUrl, 'method' => 'POST', 'finishOnKey' => '#'));
        $gather->say('〇〇についてお問い合わせのかたは、1、を、△△のお問い合わせのかたは、2、を、押してください。',['voice' => 'man', 'language' => 'ja-jp']);
        $response->redirect($endUrl);

        return response($response, 200)->header('Content-Type', 'application/xml');

}

```
番号を収集する際にはTwimlの[gather](https://jp.twilio.com/docs/voice/twiml/gather)タグを使用します。
gatherの設定については下記です。

| パラメーター | 設定内容 |
|:-:|:-:|
| numDigit  | 入力できる数字の桁数  |
| action  | 指定した桁数分入力が完了したときにリクエストを送るURL  |
| method  | actionで指定したURLにリクエストを送る際のmethod  |
| finishOnKey  | gatherの処理を終了するキー  |

```php
$gather = $response->gather(array('numDigits' => 1, 'action' => $zipcodeUrl, 'method' => 'POST', 'finishOnKey' => '#'));
$gather->say('〇〇についてお問い合わせのかたは、1、を、△△のお問い合わせのかたは、2、を、押してください。',['voice' => 'man', 'language' => 'ja-jp']);
```
今回の設定だと、自動音声を読み上げ中、読み上げ後に　1桁の番号の入力があったときに、zipcodeにリクエストを送るということになります。



その他のタグについてです。

```php
$gather->say('〇〇についてお問い合わせのかたは、1、を、△△のお問い合わせのかたは、2、を、押してください。',['voice' => 'man', 'language' => 'ja-jp']);
```
[say](https://jp.twilio.com/docs/voice/twiml/say)はテキストを読み上げるタグです。
渡したテキストを読み上げてくれます。（これが、日本語を読ませるとなかなかクセのある読み方をしてくれますw）voiceで声の設定（男性、女性など）、languageで言語を設定できます。
「お問合せの方」が「お問合せのかた」となっているのは「方」を「ほう」と呼んでしまうためです。
また、読点を多く入れているのは、聞き取りやすくするためです。読み上げるスピードが速いため、聞き取りづらいと思うところに読点を入れています。

```php
$response->redirect($endUrl);
```
[redirect](https://jp.twilio.com/docs/voice/twiml/redirect)タグは指定したURLにTwilioからリクエストを送ることができます。gatherと一緒に設定しておくとfinishOnKeyに設定されたキーを入力したり電話を切った場合に、設定したURLにTwilioがリクエストを送ってくれます。
今回は、endにリクエストを送るようにします。

####zipcodeの実装
zipcodeでは「郵便番号を7桁で・・・」が流れた後に７桁の郵便番号を入力したらconfirmに、#を入力 or 電話を切った場合にはendにリクエストを送るようなTwimlを返します。

```php
use Twilio\Twiml;

public function zipcode(Request $request){

        $incomingUrl = incomingのURL;
        $endUrl = endのURL;

        if ($request->has('Digits')) {
            if ($request->Digits == 1) {
                //1を入力
                //1を入力したときにやりたい処理を書いてください。Twimlのtransmissionを使って電話転送や、DBになんの問い合わせがあったか残しておくなど
            } elseif($request->Digits == 2) {
                //2を入力
                //2を入力したときにやりたい処理を書いてください。Twimlのtransmissionを使って電話転送や、DBになんの問い合わせがあったか残しておくなど
            } else {
                //1,2,#以外のボタンを押した場合
                $response = new Twiml();
                $response->redirect($incomingUrl);
                return response($response, 200)->header('Content-Type', 'application/xml');
            }
        }

　　　　　　　　　　　　　　　　$response = new Twiml();
        $gather = $response->gather(array('numDigits' => 7, 'action' => $confirmUrl, 'method' => 'POST', 'finishOnKey' => '#'));
        $gather->say('郵便番号を７桁で入力してください。わからない場合はシャープを押してください。',['voice' => 'man', 'language' => 'ja-jp']);

        $response->redirect($storeUrl);

        return response($response, 200)->header('Content-Type', 'application/xml');

}

```
コードの説明です

```php
        if ($request->has('Digits')) {
            if ($request->Digits == 1) {
                //1を入力
                //1を入力したときにやりたい処理を書いてください。Twimlのtransmissionを使って電話転送や、DBになんの問い合わせがあったか残しておくなど
            } elseif($request->Digits == 2) {
                //2を入力
                //2を入力したときにやりたい処理を書いてください。Twimlのtransmissionを使って電話転送や、DBになんの問い合わせがあったか残しておくなど
            } else {
                //1,2,#以外のボタンを押した場合
                $response = new Twiml();
                $response->redirect($incomingUrl);
                return response($response, 200)->header('Content-Type', 'application/xml');
            }
        }
```
gatherのactionで設定したURLに返されたリクエストにはDigits（入力した番号）が返ってきます。
なので、Digitsの存在チェックと、何が返ってきたかの判断をしています。今回は何もしませんが、1、2の場合に特定の処理をしたい場合はここに記述します。１、２、＃以外の値が返ってきた場合はredirectのTwimlを返して 「◯◯にお問い合わせの方は1を、△・・・」に戻るようにしています。

```php
        $response = new Twiml();
        $gather = $response->gather(array('numDigits' => 7, 'action' => $confirmUrl, 'method' => 'POST', 'finishOnKey' => '#'));
        $gather->say('郵便番号を７桁で入力してください。わからないばあいはシャープを押してください。',['voice' => 'man', 'language' => 'ja-jp']);
```
ここは先程と変わりませんが、テキストで「＃」を渡すと「シャープ」と呼んでくれないので、カタカナにしています。（＃で渡した時は何て読んでいるのかわからなかった、、）

####confirmの実装
confirmでは「郵便番号はXXXXXXXで・・・」が流れた後に１を入力 or #を入力 or 電話を切った場合にはendに、2を入力したらzipcodeにリクエストを送るようなTwimlを返します。

```php
use Twilio\Twiml;

public function confirm(Request $request){

        $endUrl =　endのURL;
　
        if ($request->has('Digits')) {
            $readingZipCode = "";
            $zipArray = str_split($request->Digits);
            //何百何万・・・と読み上げるので「、」区切りの文字列に変換
            $readingZipCode = implode('　、　', $zipArray);
            //０を「れい」と読み上げ、聞き取り辛いので、0を「ゼロ」に変換
            $readingZipCode = str_replace('0','ゼロ',$readingZipCode);
            //７を「しち」と読み上げ、聞き取り辛いので、7を「ナナ」に変換
            $readingZipCode = str_replace('7','ナナ',$readingZipCode);
        }


        $response = new Twiml();
        $gather = $response->gather(array('numDigits' => 1, 'action' => $endUrl, 'method' => 'POST', 'finishOnKey' => '#'));
        $gather->say('郵便番号は、 '.$readingZipCode.'、でよろしいですか、よろしければ、１、を、入力しなおす場合は、２、を押してください。',['voice' => 'man', 'language' => 'ja-jp']);
        $response->redirect($endUrl);

        return response($response, 200)->header('Content-Type', 'text/xml');

}

```

コードの説明です

```php
        if ($request->has('Digits')) {
            $readingZipCode = "";
            $zipArray = str_split($request->Digits);
            //何百何万・・・と読み上げるので「、」区切りの文字列に変換
            $readingZipCode = implode('　、　', $zipArray);
            //０を「れい」と読み上げ、聞き取り辛いので、0を「ゼロ」に変換
            $readingZipCode = str_replace('0','ゼロ',$readingZipCode);
            //７を「しち」と読み上げ、聞き取り辛いので、7を「ナナ」に変換
            $readingZipCode = str_replace('7','ナナ',$readingZipCode);
        }
```
ここは、入力された郵便番号を聞き取りやすく読ませるための処理です。
受け取った値をそのまま渡すと何百何万、、、と読み上げてしまう(1234507なら百二十三万四千五百七)ので一文字ごとに読点を入れています。(1234507 => １、２、３、４、５、0、７)
また、０を「れい」、７を「しち」と読み上げるので、聞き取りやすいよう、「ゼロ」、「ナナ」と置き換えています。
（最終的には　1234507 => １、２、３、４、５、ゼロ、ナナ）


####endの実装
endでは　2が入力されていた場合にはconfirmにリクエストを送り、それ以外の場合には「サポートセンターから・・・」を流した後に電話を切るTwimlを返します。

```php
use Twilio\Twiml;

public function end(Request $request){

        $zipcodeUrl　= zipcodeのURL;
        $confirmUrl　= confirmのURL;

        $response = new Twiml();
        if ($request->has('Digits')) {
            if ($request->Digits == 1) {
                //1を入力->郵便番号確定
                $response->say('ありがとうございます。　サポートセンターからご連絡させていただきます。',['voice' => 'man', 'language' => 'ja-jp']);
                
            } elseif($request->Digits == 2) {
                //2を入力->再入力
                $response->redirect($zipcodeUrl);

            } else {
                //関係ないボタンを押した
                $response->redirect($confirmUrl);
            }
        }else{
            //番号の入力がない場合(相談内容の選択、郵便番号の入力で#を押した等)
            $response->say('サポートセンターからご連絡させていただきます。',['voice' => 'man', 'language' => 'ja-jp']);
        }

        return response($response, 200)->header('Content-Type', 'application/xml');

}

```
コードの説明です。

```php
        $response = new Twiml();
        if ($request->has('Digits')) {
            if ($request->Digits == 1) {
                //1を入力->郵便番号確定
                $response->say('ありがとうございます。　サポートセンターからご連絡させていただきます。',['voice' => 'man', 'language' => 'ja-jp']);
                
            } elseif($request->Digits == 2) {
                //2を入力->再入力のためzipcodeへ
                $response->redirect($zipcodeUrl);

            } else {
                //関係ないボタンを押した->再度confirmへ
                $response->redirect($confirmUrl);
            }
        }else{
            //番号の入力がない場合(相談内容の選択、郵便番号の入力で#を押した等)
            $response->say('サポートセンターからご連絡させていただきます。',['voice' => 'man', 'language' => 'ja-jp']);
        }
```
endに関しては、他のURLで＃を押したときにも実行されるのでDigitがリクエストに存在しなかった場合にも自動音声を流すようにsayを返すようにしています。
また、say以外のタグ(gatherやredirect)を設定していないので、自動音声を流した後に通話が切れます。

##3.Twilio側の設定
Twilioの管理画面からwebhookを設定します。
画像赤枠の箇所にURLを記述します。
![スクリーンショット 2021-11-26 22.00.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2286117/e202ae2f-0d8d-33c3-7b74-5ad50426ed14.png)

実装については以上です。

##４.最後に
今回は、電話がかかってきた相手に対して、メッセージを流して、郵便番号を入力してもらう処理に関してだけ書きました。
＄request->Fromでかけてきた電話番号を取得することもできるので、電話番号以外に必要な情報を数字と紐付けて選択してもらうことで電話経由でのコンバージョンを獲得することもできます。
また、一回の通話に対して、ユニークなIDが割り当てられる（$request->CallSidで取得できます）ので、それを利用して一度入力された値を書き換えるということにも対応できます。
他にも様々な機能がりますし、SMSの方でも色々なことができるので、電話、SMSで何かしたいときには利用を検討してみるといいと思います。

##おまけ
今回はガッツリPHPを使って実装しましたが、[Twilio Studio](https://cloudapi.kddi-web.com/function/studio)を使っても同じようなことができます。
こちらはエンジニア以外の方でも触れやすいと思います。