Vonageを使って「ずんだ日常電話相談」を作ろうとしてみた話

# はじめに
ご覧いただきありがとうございます。
こちらは「[Vonage Advent Calendar 2023](https://qiita.com/advent-calendar/2023/vonage)」の14日目の記事になります。

VonageのVoice APIを使って、ずんだもんにお悩み相談をすることができる、「ずんだ日常電話相談」を作ろうとしました。

# 結論
**情報が少なすぎて動かせなかった！**

かなり良い所まで出来たと思うのですが、ずんだもんにお悩み相談をすることはできませんでした…

以下は私が歩んだ道のりになります。良かったら見ていっていただけると嬉しいです。

:::note info
**この記事で分かること**
Pythonを使用し、Vonageから電話を掛ける/音声合成をする/音声認識をする方法
:::
:::note warn
**この記事で分からないこと**
Vonageで任意の音声を再生する/音声認識を繰り返し行う方法
:::

# 何をしたいか
ほとんどの人間は日々「悩み」を抱えて生きていますが、その「悩み」は誰かに相談することで解消されることがあります。
また、何か「考え」がまとまらないときに、誰かにその内容を話すことで、自分の中で「考え」がまとまることもあります。

皆さんもそのような経験があるのではないでしょうか。

しかし、誰かに相談するということは、その「誰か」の時間を使ってしまうことになりますし、まず「相談する」ということ自体ハードルが高いことだと感じます。

そんな時、いつもの**ずんだもん**[^KEY]に相談できたら心強くないでしょうか。
人に話しにくいことでも、深夜だったとしても、ずんだの妖精が相談に乗ってくれます。

ということで「ずんだ日常電話相談」をVonage Voice APIで作っていきます。

# Vonage Voice APIとは
## Vonageについて
[Advent Calenderのページ](https://qiita.com/advent-calendar/2023/vonage)に分かりやすく書かれていましたので引用します。

>Vonageは、オムニチャンネルを容易に実装可能なAPIをグローバルで提供している会社です。世界中で多くの方が使っているPSTNの電話やSMSをAPIで組み込む事が可能です。電話やSMSのみならず、Videoや各種メッセージチャネルや二要素認証を提供しています。

自前で用意するのが面倒な「電話を利用したサービス」を簡単に作れるってことですね

## Voice APIについて
Voice APIはVonageが提供しているAPIサービスの一つで、音声通話を利用できます。今回はVoice APIの機能の一つ、音声認識機能を利用して、ずんだもんと話せるようにしたいと思います。

https://www.vonagebusiness.jp/communications-apis/voice/features/automatic-speech-recognition/

**しかし、ChatGPTのようなAIを組み込むのも手間がかかるので、「こちらが話したという事実」に対してずんだもんが相づちをしてくれる相談所を作りたいと思います。**

## 登録について
メールアドレスと名前、電話番号が必要です。

### 登録ページ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2921137/74fb53b2-1f35-9051-0684-0d2f33efdf37.png)
[Advent Calenderのページ](https://qiita.com/advent-calendar/2023/vonage)や[公式サイト](https://www.vonage.com/)からアカウント登録ページに飛び、必要事項を入力した後、登録ボタンをクリックします。

### メアドの認証
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2921137/b4ca1288-77d1-f651-5e92-4797f1a971e1.png)
先ほど入力したメールアドレスに認証のためのリンクが届くので開きます。

### 電話の認証
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2921137/3d53dc79-8c75-103a-fd3e-3b8ffad2868d.png)
開いたページで電話認証をします。

### ダッシュボード
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2921137/8d1453b3-0c2d-3151-3636-a50a5997151e.png)

[Advent Calenderのページ](https://qiita.com/advent-calendar/2023/vonage)に書かれていたクーポンコードを入力したら本当に10ユーロ追加で付与されました。ありがとうございます…
登録時に貰える2ユーロと合計して12ユーロ、日本円に換算すると大体1,863円くらいになります(2023年12月15日時点)。

クーポンコードの適用方法は、[Vonage Advent Calendar 2023](https://qiita.com/advent-calendar/2023/vonage)の1日目(シリーズ2)、@mobilebizさんの記事が分かりやすかったので紹介させていただきます。

https://zenn.dev/kwcplus/articles/how-to-redeem-coupon-code

これで登録完了です、

## どんな設計にするか
電話相談ならやはり質問者側から電話を掛けたいところですが、日米間の国際通話になると実験時に普段と違う電話代が掛かってしまう(固定電話を使用するため親に不審な請求と思われてしまう)ため、Vonageから自分の家の固定電話にかけてもらうようにしたいと思います。

1. プログラムを実行する
1. Vonageからの通信を受けるサーバーを動かす
1. Vonageから電話がかかってくる
1. 電話を受けるとずんだもんの声（あいさつ）が再生される
1. ずんだもんに対して相談をする
1. VonageのVoice APIの音声認識機能で話された内容が、サーバーに送られてくる
1. ずんだもんの声（相づち）をVonageに再生させる
1. 5~7を繰り返すことで(一方的に)相談ができる

流れとしてはこんな感じです。

# コードの作成
## 環境について
Vonageからの通信をWebhookで受ける必要があるのですが、ngrokを使うにしても、普段使っている環境をインターネットに公開するのは気が引けたので、VirtualBoxをインストールし、その上にUbuntuをインストールして使用していきます。

参考にさせていただいた記事はNode.js/TypeScript/Express.jsを使って実装しているのですが、私がサーバーサイドのJavaScriptに大変疎いので、今回はPythonで実装していきたいと思います。

しかし、Vonage × Voice API × Pythonを扱った記事が非常に少なく、特に日本語圏に限るとこれっぽっちも見つかりません

ほぼ唯一の資料、公式ドキュメントを参考に進めていきます。
https://developer.vonage.com/ja/getting-started/overview?source=voice

https://github.com/Vonage/vonage-python-sdk

## Vonageの設定
1. [APIダッシュボード](https://dashboard.nexmo.com/)にアクセスし、API keyとAPIシークレットをメモしておきます
1. テストクレジットをありがたく使わせていただき、電話番号をレンタルします
    @PAiDoXさんの[こちらの記事](https://qiita.com/PAiDoX/items/3eca06594152bb3a833f#準備編)が分かりやすかったです
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2921137/5120843a-0316-cbcd-237b-1261768b21b3.png)
    レンタルした電話番号もメモしておきます

1. 次に、アプリケーションの作成を行います
[アプリケーション作成ページ](https://dashboard.nexmo.com/applications)へアクセスし、任意の名前でアプリケーションを作成します。アプリケーションIDが表示されるのでメモし、秘密鍵をダウンロードします。画面下部で電話番号をリンクさせます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2921137/90177071-4e32-8805-b210-5665c2152ab0.png)


## ローカルでの作業
次はホームフォルダに作業用のフォルダを作ります
名前は何でも大丈夫です.
``` bash
mkdir zunda
cd zunda
```

次に必要なソフトウェアをインストールします。
``` bash
sudo apt update
sudo apt upgrade
sudo apt install python3-pip

pip install vonage python-dotenv
```
これでPythonからVonageを扱うことができるようになりました。
とりあえずVonageから電話を掛けてみたいと思います。
### サンプルコードの実行
[GitHub](https://github.com/Vonage/vonage-python-code-snippets/blob/89d83ba/voice/make-outbound-call-ncco.py#L22-L30)のサンプルコードを利用します。
以下のコードは上記のページに掲載されているものです。

``` python:sample.py
#!/usr/bin/env python3
from pprint import pprint
import os
from os.path import join, dirname
from dotenv import load_dotenv
import vonage

dotenv_path = join(dirname(__file__), "../.env")
load_dotenv(dotenv_path)

VONAGE_APPLICATION_ID = os.environ.get("VONAGE_APPLICATION_ID")
VONAGE_APPLICATION_PRIVATE_KEY_PATH = os.environ.get("VONAGE_APPLICATION_PRIVATE_KEY_PATH")

VONAGE_NUMBER = os.environ.get("VONAGE_NUMBER")
TO_NUMBER = os.environ.get("TO_NUMBER")

client = vonage.Client(
    application_id=VONAGE_APPLICATION_ID,
    private_key=VONAGE_APPLICATION_PRIVATE_KEY_PATH,
)

response = client.voice.create_call({
    'to': [{'type': 'phone', 'number': TO_NUMBER}],
    'from': {'type': 'phone', 'number': VONAGE_NUMBER},
    'ncco': [{'action': 'talk', 'text': 'This is a text to speech call from Nexmo'}]
})

pprint(response)
```

こちらのファイルはUbuntu上に保存しておきます。
次に、先ほどダウンロードした秘密鍵(名前は初期状態でprivate.keyになっているはず)をUbuntu上に配置します。
仮想マシンの場合は、手元の作業用PCのメモ帳等で秘密鍵ファイルを開いて内容をコピーし、仮想マシン上で開いたテキストエディタにペーストして拡張子.keyで保存するのが手軽だと思います。

続いて、環境変数を設定していきます。
認証情報は実行ファイルにハードコーディングせず、環境変数にして定義しておくべきです。
以下のスクリプトは例です。ここまでメモしてきた情報で適宜置き換えてください

```
export VONAGE_APPLICATION_ID=アプリケーションID
export VONAGE_APPLICATION_PRIVATE_KEY_PATH=~/zunda/private.key #秘密鍵ファイルのパス

export VONAGE_NUMBER=*********** #レンタルした電話番号
export TO_NUMBER=81************* #電話を掛ける先の電話番号
```

最初の81は日本の国番号です。もし日本国外の方がこの記事を読まれていたら、その番号も変更する必要があるかもしれません。
その先に入力する番号は、例えば03-0123-4567に掛けたい場合、301234567になります。市外局番などの最初の0を省いて入力してください。

私はアプリケーションIDを入力すべきところにAPI Keyを入力してしまったり、秘密鍵を指定すべき箇所で公開鍵を指定してしまい、かなり手間取りました。

sample.pyを実行し、指定した電話にVonageから電話が掛かってきたら成功です。

では今度はずんだもんが応答するようにしていきましょう。
### ずんだもんとの会話
#### Webhookを受け取る
ngrokをインストールします。
ngrokを利用することで、ファイアウォールに穴を開けることなく、自宅のサーバーをインターネットに公開することができます。詳しい話はググってください。

[公式サイト](https://ngrok.com/download)に掲載されているいずれかの方法でngrokのソフトウェアをインストールしてください。
ubuntuの場合はsnapが便利だと思います。

続いて、PythonでWebhookを受け取れるようにします。

https://zenn.dev/yu1low/articles/459fc8023e80a2

こちらの記事のコードをそのまま拝借させていただきました。
``` python:wbhkReceiver.py
# -*- coding:utf-8 -*-
import http.server
import socketserver
import datetime
from urllib.parse import parse_qs, urlparse

class MyHandler(http.server.BaseHTTPRequestHandler):
    def do_POST(self):
        print('path = {}'.format(self.path))
        parsed_path = urlparse(self.path)
        print('parsed: path = {}, query = {}'.format(parsed_path.path, parse_qs(parsed_path.query)))
        print('headers\r\n-----\r\n{}-----'.format(self.headers))
        content_length = int(self.headers['content-length'])
        
        #Body書き出し
        now = datetime.datetime.now()
        file_name = 'wbhk_out_' + now.strftime('%Y%m%d_%H%M%S') + '.json'
        req_body = self.rfile.read(content_length).decode("utf-8")
        with open(file_name, 'w') as f:
            f.write(req_body)
        
        self.send_response(200)
        self.send_header('Content-Type', 'text/plain; charset=utf-8')
        self.end_headers()
        self.wfile.write(b'Hello from do_POST')

with socketserver.TCPServer(("", 80), MyHandler) as httpd:
    httpd.serve_forever()
```

このファイルを保存し、実行します。使用するポート番号は適宜変更してください。

ngrokを実行します。無料のユーザー登録をすることによって機能が増えるそうですが、今回は割愛します。
``` bash
ngrok http 80 #80は公開するポート番号
```

上記のコードを実行するとngrokからドメインが.ngrok.ioなURLが吐き出されるのでメモしておきます。
このURLをVonageに送信するように、sample.pyの一部を書き換えます。
``` python:sample.py
response = client.voice.create_call({
    'to': [{'type': 'phone', 'number': TO_NUMBER}],
    'from': {'type': 'phone', 'number': VONAGE_NUMBER},
    'ncco': [{"action": "talk","text": "こんにちは、ずんだもんなのだ！","level": 1,"language": "ja-JP","bargeIn": "false"},{"action": "input","type": ["speech"],"speech":{"endOnSilence": 2.0,"language": "ja-JP"},"eventUrl": ["https://(吐き出されたURL)"]}]
})
```

書き換えた後、sample.pyを実行すると、Vonageから電話が掛かってきます。
"こんにちは、ずんだもんなのだ！"という声が聞こえた後、電話に話しかけてください。

その内容が文字起こしされ、jsonの形式で送られてきます。正しく動いていれば、作業用のフォルダにwbhk_out_(日付と時刻).jsonというファイルが生成されているはずです。このファイルの中に、先ほど話しかけた内容が書かれていたら成功です。

#### ずんだもんと話す
ずんだもんが相づちする声を生成しておきます。VOICEVOX ずんだもんなら無償で利用できます。
今回は非公式の[WEB版VOICEVOX](https://www.voicevox.su-shiki.com/)を利用させていただきました。

挨拶用と相づち用で、計2本の音声ファイルを作成しました。
Vonageで音声をストリーミング再生するには、インターネットからアクセスできる場所に公開されている必要があるようなので、適当なサーバー(今回はGitHub)に上げておきます。
ngrokを動かしているので、ファイルサーバーを立てるのもアリだとは思いますが、セキュリティリスクも当然上がります。

sample.pyを書き換えて、zunda.pyを作成します。

:::note warn
以下のコードは正常に動作しません
:::

``` python:zunda.py
#!/usr/bin/env python3
from pprint import pprint
import os
from os.path import join, dirname
from dotenv import load_dotenv
import vonage

import http.server
import socketserver
import time
from urllib.parse import parse_qs, urlparse

ngrokurl = "https://(吐き出されたURL)"

dotenv_path = join(dirname(__file__), "../.env")
load_dotenv(dotenv_path)

VONAGE_APPLICATION_ID = os.environ.get("VONAGE_APPLICATION_ID")
VONAGE_APPLICATION_PRIVATE_KEY_PATH = os.environ.get("VONAGE_APPLICATION_PRIVATE_KEY_PATH")

VONAGE_NUMBER = os.environ.get("VONAGE_NUMBER")
TO_NUMBER = os.environ.get("TO_NUMBER")


    
client = vonage.Client(
    application_id=VONAGE_APPLICATION_ID,
    private_key=VONAGE_APPLICATION_PRIVATE_KEY_PATH,
)

stream_url_hello = 'https://github.com/kazu-iroiro/zunda_resource/raw/main/hello.wav'
stream_url_un = 'https://github.com/kazu-iroiro/zunda_resource/raw/main/un.wav'

# 通話を開始
response = client.voice.create_call({
    'to': [{'type': 'phone', 'number': TO_NUMBER}],
    'from': {'type': 'phone', 'number': VONAGE_NUMBER},
    'answer_url': [ngrokurl]
})
# 通話の情報を取得
client.voice.get_calls()
pprint(response)

# Vonageから返ってきた情報から通話を識別するUUIDを抽出
uuid = response["uuid"]
time.sleep(3)

# UUIDに対してずんだもんの声を再生(するはずだった)
client.voice.send_audio(uuid,stream_url=[stream_url_hello])

pprint(response)
```

動かす方法をご存じの方がいらっしゃったら、コメントをしていただけると嬉しいです。

# 最後に
公式ドキュメントを解読することができず、「ずんだ日常電話相談」を作ることはできませんでした。

しかし、Pythonから電話を掛ける体験はなかなか興味深かったです。今回記事中では扱いませんでしたが、SMSを送信することも簡単にできました。
この記事を読んで少しでもVonageに興味が沸いた方がいらっしゃれば嬉しいです。

最後まで読んでいただきありがとうございました！


# 参考文献
https://web116.jp/2024ikou/price/inter.html

https://www.vonagebusiness.jp/communications-apis/voice/features/automatic-speech-recognition/

https://zenn.dev/kwcplus/articles/how-to-redeem-coupon-code

https://developer.vonage.com/ja/getting-started/overview?source=voice

https://github.com/Vonage/vonage-python-sdk

https://github.com/Vonage/vonage-python-code-snippets/blob/89d83ba/voice/make-outbound-call-ncco.py#L22-L30

https://qiita.com/PAiDoX/items/3eca06594152bb3a833f#fnref-5

https://ngrok.com/download

https://zenn.dev/yu1low/articles/459fc8023e80a2


[^KEY]: 「ずんだもん」はずんだもちの妖精。絵や動画がネットの海に沢山放流されている。