Twilioを使って電話でパスワードを伝えるシステムを作った

[Twilioオンラインコンテスト2021](https://cloudapi.kddi-web.com/twilio-online-contest-2021)の6月分に応募するために「[PPAPTwilio - パスワードは電話で送ろう](https://protopedia.net/prototype/2316)」を作りました。
TwilioのCLI、Studio、Functions、Syncを使ったので、気になったことをまとめておきます。

# 何を作った？
 - Twilioでパスワードを伝えるシステム
 - 送信者は、相手の電話番号とパスワードを登録する
 - 受信者は、システムに電話をかけると、パスワードが再生される

<iframe width="560" height="315" src="https://www.youtube.com/embed/4tMcS9srF90" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

※電話番号を変えたので、動画内の番号に発信してもシステムは利用できません。

# Twilioの各サービスを利用してみて
## Twilio Studio：システムフローの制御
システムの処理フローは、GUIで作成できるTwilio Studioで作成しました。
自動音声の文言をその場で編集したり、外部のAPIを呼び出したりもできるので便利です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20639/6c75d633-5280-1874-323a-2f14e137e111.png)

## Twilio CLI
DB(Sync)を使うなど少し複雑なことをしたい場合は、コードを書くことになります。
Web上でも作成可能ですが、使い慣れたVisual Studio Codeなどを使いたかったので、Twilio CLIを使ってローカルに開発環境を作りました。ローカルで開発、デバッグ、デプロイが可能なのでおすすめです。
環境セットアップ手順は[Twilio Serverless CLI V.2.x](https://qiita.com/mobilebiz/items/0b599d31b64e62873c7a)を参考にさせて頂きました。

## Twilio Functions：コードによる制御
Syncへのデータ保存など少し複雑な処理は、サーバーレス環境であるTwilio Functionsを使いました。

### StudioからFunctionsへの引数の渡し方・受け取り方
StudioからFunctionsを呼び出せますが、引数を渡したいことがあります。今回の場合は、相手の電話番号やパスワードを録音した音声URLを渡す必要がありました。

#### Studio
「Function Parameters」に引数の名前と値を書きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20639/0b2450be-26c1-6587-8bf4-500b9314d5a1.png)


#### Functions
Studioから渡された引数は、eventに入っています。

```JavaScript
exports.handler = function (context, event, callback) {
  // Studioから渡された引数は event に入っている
  console.log(event.voiceUrl);
}
```

## Twilio Sync(DB)：電話番号などを保存
相手の電話番号などを保存するために、Twilio Sync（DB）を使いました。
Syncにはデータの種類によりいくつかの保存形式がありますが、今回は電話番号をキーにして情報を保存すればよかったためMapを使用しました。

### ライブラリの利用
Functionsから利用する場合、ライブラリ（[Twilio Sync Library for TwilioCLI](https://qiita.com/mobilebiz/items/427e34d1da80ed1f72e9)）が公開されているのでこちらを利用しました。

```javascript
// ファイル格納や環境変数の準備が別途必要です
// 詳細は上記ライブラリページを参照

exports.handler = async function (context, event, callback) {
  // AssetsからTwilioSyncライブラリの読込
  const assets = Runtime.getAssets();
  const TwilioSync = require(assets["/twilioSync.js"].path);
  const twilioSync = new TwilioSync(
    context.MASTER_ACCOUNT_SID,
    context.ACCOUNT_SID,
    context.AUTH_TOKEN,
    context.SYNC_SERVICE_SID,
    true
  );

  // Map名
  const MAP_NAME = "passwordRecording";

  await twilioSync
    .getMap(MAP_NAME)
    .then(async (map) => {
      const mapObj = await twilioSync.getMapItems(MAP_NAME);
      let result = "";

      for (const obj of mapObj) {
       // Mapのデータ処理        
      }
      return result;
    })
    .catch(async (err) => {
      console.log("Map not found.");
      throw new Error(err);
    })
    .then((res) => {
      callback(null, {
        result: res,
      });
    });
}
```
## デバッグ
処理中にエラーが発生した場合は、Twilioコンソール画面の右上のアイコンが赤くなるので、ここから状況を確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20639/ec7eb32a-ac92-b36d-2406-5e4199d7eb91.png)

エラーが発生していなくても、Programmable Voice→通話→コールログから各通話で呼び出したサービスの入出力を確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/20639/04517ad6-222b-14a2-1be6-eb1403d64bf1.png)


# まとめ
今回は、Twilioを使って電話でパスワードを伝えるシステムを作りました。
Studio、Functions、Syncを使うことでTwilio内で完結させることができることが分かり、各サービスへの理解が深まりました。