TwilioをAzure Functions(C#)で動かす

`Twilio`を`Azure Functions`と`C#`で、使用するためのまとめです。
備忘録的な内容なので細かな部分において、説明が雑なところがありますがご了承ください。

# 事前準備について
TwilioやAzureのアカウント登録、VSCodeの拡張機能のインストール方法はここでご説明致しませんのでQiita等でお調べください。

* [Twilioアカウント](https://qiita.com/mobilebiz/items/932eb21ff6ed36f524d8)　<br>@mobilebizさんをフォローするといろいろ情報が集まると思います。
* [Azureアカウント](https://docs.microsoft.com/ja-jp/learn/modules/create-an-azure-account/)
* [Visual Studio Code](https://azure.microsoft.com/ja-jp/products/visual-studio-code/)　<br>開発環境（VS2019でもいいですが、私の環境がmacなので・・）
* [Azure Functions拡張機能](https://docs.microsoft.com/ja-jp/azure/developer/javascript/tutorial/vscode-function-app-http-trigger/tutorial-vscode-serverless-node-install?tabs=bash)　<br>
Azure Functionsとの連動などで使用します。
* [Azure Functions Core Tools](https://github.com/Azure/azure-functions-core-tools) <br>
ローカルの実行で使用します。
* [Nuget拡張機能]( https://zenn.dev/midoliy/articles/9e3cff958ff89ba151de#%F0%9F%94%B5nuget-package-manager-(optional)) <br>
私はNuGet Gallery使ってます。
* [その他の拡張機能](https://zenn.dev/midoliy/articles/9e3cff958ff89ba151de) <br>
いろいろ開発に便利なものがあるのでお好みで。

# 手順
## Azure Functionsのプロジェクトを作成する
Azure Functionsの拡張機能を使用して、Azure Functionsのプロジェクトを作成します。

まずはAzureのタブを選び、稲妻マークをクリックします。
![プロジェクト作成](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189829/7f68aceb-6f63-db09-959e-dbf606c675c0.png)

「Create new project」を選択し、保存先のパスを指定します。
![プロジェクト作成](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189829/074aa865-4446-582a-57d9-2ff5e7dfc359.png)

プロジェクトの設定を行います。今回は`C#`を使用したいので、`C#`を選びます。
![言語設定](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189829/30a42792-7302-dc3a-aba4-d2b3333dafc5.png)

`C#`のランタイムをしています。今回は、`.NET Core 3 LTS`を選んでいます。
![言語設定](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189829/507a2c6d-30b1-3689-a980-a0fb95c05567.png)

Twilioから　呼び出してもらうため、`HTTP trigger`を選びます。
![種類設定](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189829/d001be67-21a7-9735-3800-e9f41f746ce6.png)

APIの名前を指定します。今回は、`twiliosample`を入力しています。
![ランタイム設定](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189829/0a559ddb-3388-e05b-edf0-ad158c78798a.png)

namespaceを指定します。今回は、`masaya3.twilio`を入力しています。
![namespace設定](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189829/86ad6804-d641-31d3-8fb1-21ad6c96ad47.png)

関数の[アクセスの制約](https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-bindings-http-webhook-trigger?tabs=csharp)を指定します。今回は、初期値である`Function`を選んでいます。
![アクセス設定](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189829/63c91243-15f9-b83a-6418-7d429e1b5178.png)

最後に、新しいウィンドウでプロジェクトを開くかを選びます。今回は、現在のウィンドウをそのまま使用しています。
![状態設定](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189829/4d69fc0b-48e4-5f58-9e56-1643672c943a.png)

問題がなければ、Azure Functionsのプロジェクト作成されます。
![完成](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189829/6e18c5c2-a67f-cf96-2bb7-4a142b4c8297.png)

### 動作検証 

作成したプロジェクトが動くか確認してみます。
`F5`を押すことで、ビルドされ、問題がなければローカルのサーバが起動します。
※ローカルで動かすには、[Azure Functions Core Tools](https://github.com/Azure/azure-functions-core-tools) が必要です。もしインストールされていない場合は、エラーがでるので、エラー内容にそって設定してください。


起動すると、TERMINALにローカルサーバのURLが表示されます。

```shell
twiliosample: [GET,POST] http://localhost:7071/api/twiliosample
```

TERMINALに表示されているURLに`Ctrl`を押しながらクリックするか、URLをブラウザで入力しアクセスすると動きを見ることができます。

![実行](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189829/282c521c-9b41-5409-f8df-ebbfde95affd.png)

正常に実行されると以下のような文字が表示されます。
![　実行完了](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189829/b83df2c7-09ce-9563-47a7-687ee300b670.png)

### TwilioのSDKをインストール

Azure Functionsのベースができましたので、次はTwilioのライブラリをインストールします。

使用するライブラリはこちら

https://www.nuget.org/packages/Microsoft.Azure.WebJobs.Extensions.Twilio

VSCodeで`F1`を押して、メニューから`Nuget:Open Nuget Gallery`を選択します。
![拡張機能　](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189829/215a3d7f-c6eb-2f75-5e4c-92f99f45eb2c.png)

メニューから、`Microsoft.Azure.WebJobs.Extensions.Twilio`を探し、　右のプロジェクト名にチェックをつけて、「Install」をクリックしてインストールします。
※Twilioだけだといっぱい検索に引っかかるので、`Microsoft Twilio`等で検索してみてください。
![拡張機能　](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189829/d5a39afc-9eed-d36b-968f-17369785f4de.png)


## コードを書く
ライブラリもインストールできたので、実際のコードを記述していきます。

今回説明しているサンプルコードは[こちらで公開](https://github.com/tanaka3/twilio_azure_sample)しています。

### Twilioからの情報取得する

API呼び出し時にTwilioから渡されるデータを取得します。
以下は、電話番号を取得しています。

```csharp:twiliosample.cs
string from = req.Form["From"];   
```

データは、`Form-Data`に格納されていますので、そこから必要となるデータを取得してください。

どのようなデータが取れるかは、以下のリファレンスを参照してください（これいつも忘れる・・）

https://jp.twilio.com/docs/voice/twiml#request-parameters


### 発話処理を書く
電話の応答などは、[TwiML](https://jp.twilio.com/docs/voice/twiml)を使って記述します。
直接、XMLを作っても良いですが、インストールしたTwilio SDKの`VoiceResponse `で簡単に作成することができます。

VoiceResponseのSayメソッドで、応答時に話すメッセージを文字が設定できます。
今回は、発話のパラメータとして、日本語音声である` language: "ja-jp"`や、音声の種類の`voice: "alice"`を指定しています。
発話で登録可能な各種パラメータは、以下のリファレンスを参照してください。

https://www.twilio.com/docs/voice/twiml/say


`VoiceResponse.ToString()`でTwiMLが作成されます。
このTwiMLをAPIの返り値として返します。

返り値の返却方法として、`ContentResult`を使用し、必ず`ContentType = "application/xml"`を指定します。
これを指定しないとTwilio側で認識されません。

```csharp:twiliosample.cs
var response = new VoiceResponse();
response.Say(from + "から着信がありました。", language: "ja-jp", voice: "alice");

return new ContentResult{Content = response.ToString(), ContentType = "application/xml"};
```

#### vocieについて
日本語のVoiceで使用できるパラメータは、`alice`、`Polly.Mizuki`、`Polly.Takumi`です。
`man`と`woman`は指定できないため、`alice`と同じになってます。

https://twitter.com/masaya3/status/1404345884322648066


#### 入力受付を書く
今回は動かしていませんが、ボタンによる入力方法もまとめます。
ボタンの入力は、`Gather`を使用します。
これを使用することで、よくある電話を使った入力を実現することができます。
入力後は、`Action`パラメータで指定されたURLが呼び出されます。

詳しいパラメータは、以下のリファレンスを参照してください。

https://www.twilio.com/docs/voice/twiml/gather

タイムアウト後に、発話をさせたい場合は、Gather後に発話のSayを登録します。


```csharp:twiliosample.cs
var digitsResponse = new VoiceResponse();
digitsResponse.Gather(new Gather(numDigits: 入力桁数, timeout:入力のタイムアウト時間, action:"番号入力後の呼び出しURL")
                         .Say("ボタン入力を促すメッセージ"), language: "ja-jp", voice: "alice"));

digitsResponse.Say("タイムアウト後のメッセージ", language: "ja-jp", voice: "alice");

return new ContentResult{Content = digitsResponse.ToString(), ContentType = "application/xml"};
```

## ローカルで動作検証する　

Azureに登録する前に、ローカル環境で動作を検証します。
今回も`F5`を押して、ローカルサーバを起動します。

`curl`などを使用(私はPostman)して、`form-data`に`From:+8109012345678`を入力して、`POST`します。

正常に動作すれば、戻り値として、「入力された電番番号ら着信がありました」というメッセージが入ったTwiMLが表示されます。
![実行結果](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189829/090fb90a-1a9d-b430-6c22-a96d81c01b99.png)

## Azure Functionsにデプロイする
ローカルで動きが確認できたら、Azureに登録します。
VSCodeからAzureへサインインが必要となりますが、サインイン方法は、[こちら](https://news.yahoo.co.jp/articles/33a9a29ddd8262b1c94963ed37f796ed6961f4fc)を参照ください

まずは、Azureタブを開き、FUNCTIONSから以下のマークをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189829/e65ff06a-51f8-cc75-d404-42204a1c1441.png)

登録先として、ここではすでに作成済みの関数アプリ（higedaruma）を指定しています。
関数アプリから作成する場合は、こちらの[記事](https://news.yahoo.co.jp/articles/33a9a29ddd8262b1c94963ed37f796ed6961f4fc)を参照ください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189829/1df51684-e8f7-eac8-7450-a2d9364ea681.png)


問題なければ「Deploy」ボタンをクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189829/f5aac688-690b-92be-dc17-9fd1dcbb7730.png)


デプロイが成功すれば、Azureの関数アプリに`twiliosample`が追加されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189829/135b71f6-fd79-c250-e31b-b7919c30e29d.png)

### 動きを確認する
関数からtwiliosampleを開き、「コードとテスト」を選択、「テストと実行」をクリックすると、入出力の確認が確認ができます。
事前に動きを見てると、問題が発生したときの切り分けがしやすくなので一度は動作確認をしましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189829/5c4d2fa5-69d8-7cc0-257d-d195e7dfd40b.png)

## Twilioに登録する

Azureに登録した関数アプリをTwilioに登録します。

Azure Functionsのtwiliosampleから、`関数のURLの取得`を選択し、APIのURLを取得します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189829/a4550511-c207-f3b1-efa3-75bd99f5a99b.png)


Twilioの`Webhook`に先ほどコピーしたURLを登録します。
あとは、電話番号に電話をすれば動作を確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/189829/c6b630a7-f409-d313-90f1-312c996010ea.png)

# まとめ
半年ぐらいすると、この流れを忘れそうだったので、メモとして作成しました。
サーバを意識せず、デバッグ含めてローカルでできるのはかなり便利です。