https://a.storyblok.com/f/270183/69246/e281d17397/blog_family-hotline_1200x600.png

Vonageでファミリーホットラインを構築

最終更新日 May 4, 2021

所要時間:7 分

私が電話をかける会社には必ず自動応答システムがあり、実際の人と話す前に質問される。 Iと思っていた。

数ヶ月前、娘が中学校に入学したのですが、送られてきた書類に記入欄があったのは 連絡先の電話番号を記入する欄が1つだけあった。.もし私の詳細を伝えて、私が不在だったらどうなるのでしょう?ラッキーなことに、私はVonageで働いていて、どちらか一方、あるいは両方に電話を転送できる電話番号を設定する方法を知っている! Vonageが助けてくれる!

何を作るか

  1. 学校には連絡先としてVonageの番号が与えられている。

  2. この番号に電話をかけると、自動メッセージで次のような選択肢が示される:

    1. 両親の名前を列挙し、それぞれの両親に転送するために押す桁を記す。

    2. 発信者が何もしなければ、リストの最初の親に転送する。

    3. 緊急の場合は「*」を押してください。電話会議がセットアップされ、両方の両親にダイヤルされます。

もう2つほど要求があった:

  1. シンプルで、ホストも運営も実質的に無料であること。

  2. 管理するデータベースがないため、さらにシンプル。

どのように構築していくか

Python 3.6とFlaskだ。Python 3.6を選んだのは、このリリースに f-stringsを搭載したリリースだったからだ!また pipenvを使ってPythonの依存関係を管理しています。

この組み合わせにより、わずか100行強のコードで優れた家庭用IVR(対話型音声応答)システムを構築することができます!

行こう!

まずは開発環境を整えよう。

Vonageはあなたが書こうとしているサーバーを呼び出す必要があり、あなたのラップトップは安全なファイアウォールの後ろに置かれている。 ngrokをローカル開発に使いたいだろう。 をお勧めしよう。.

おそらく pipenvをグローバルにインストールし、それ以降はプロジェクトの仮想環境を管理するために使うことになる:

sudo pip install --upgrade pipenv

では、このプロジェクトに必要な依存関係をインストールし、仮想環境を有効化しよう。新しいプロジェクトのディレクトリで以下のコマンドを実行してください。 hotline:

# Install compatible versions of `vonage`, `flask` and `attrs` pipenv install vonage~=2.5.5 flask~=1.0.2 attrs~=18.2.0 # Activate the virtualenv: pipenv shell

では、Flaskサーバーを書き始めよう。

という名前のファイルを開き hotline.pyという名前のファイルを開き、次のように入力する:

from flask import Flask, jsonify, url_for as url_for_, request


app = Flask(__name__)


@app.route("/incoming/", methods=["GET", "POST"])
def incoming():
    """
    An HTTP endpoint which handles incoming calls.

    :return: A JSON HTTP response containing the main menu NCCO actions.
    """
    return jsonify(
        [
            {
                "action": "talk",
                "text": "Welcome to the Brockman family hotline",
                "voiceName": "Amy",
            }
        ]
    )

では 別々のターミナル・ウィンドウでで以下を同時に実行する:

# Tunnel requests to local port 5000 (Flask's default port): ngrok http 5000
# Start your Flask server: FLASK_APP=hotline flask run --debugger --reload

を実行すると を実行するとを実行すると、生成されたドメイン名があなたのサーバーに転送されます。それは次のようになります: https://abcde1234.ngrok.io -> localhost:5000.これで、Flaskアプリを次の場所でテストできます。 https://abcde1234.ngrok.io/incoming(abcde1234をターミナルに表示されたものに置き換えてください)。

このように表示されるはずだ:

First NCCO testFirst Test

サーバーに電話をかける

を設定する必要があります。 Vonageバーチャル番号を設定する必要があります。そのためには3つのものが必要です:

  1. A Vonageアカウント

  2. 設定されたVoiceアプリケーション

  3. 設定されたVonageバーチャル番号。

Vonageアカウントの作成

Vonage CLIをインストールする

このコマンドでVonage CLIをグローバルにインストールします:

npm install @vonage/cli -g

次に、Vonage API キーとシークレットを使って CLI を設定します。この情報は 開発者ダッシュボード.

vonage config:set --apiKey=VONAGE_API_KEY --apiSecret=VONAGE_API_SECRET

Voiceアプリケーションの作成

プロジェクト用に新しいディレクトリを作成し、そこにCDを入れる:

mkdir my_project
CD my_project

では、CLIを使ってVonageアプリケーションを作成してください。

vonage apps:create ✔ Application Name … hotline ✔ Select App Capabilities › Voice ✔ Create voice webhooks? … yes ✔ Answer Webhook - URL … https://ed330676.ngrok.io/incoming/ ✔ Answer Webhook - Method › POST ✔ Event Webhook - URL … https://ed330676.ngrok.io/event/ ✔ Event Webhook - Method › POST ✔ Allow use of data for AI training? Read data collection disclosure … yes Application created: 34abcd12-ef12-40e3-9c6c-4274b3633761

の後にプリントアウトされるIDを保存しておきたい。 Application created:.次のステップで必要になります。

Numbersを購入してIn-Appにリンクする

電話を受けるには番号が必要です。以下のコマンドで番号を借りることができます(国番号をあなたのコードに置き換えてください)。例えば、あなたがアメリカにいる場合、次のように置き換えます。 GBUS:

vonage numbers:search US vonage numbers:buy [NUMBER] [COUNTRYCODE]

その番号をアプリにリンクさせる:

vonage apps:link --number=VONAGE_NUMBER APP_ID

オーケー!

今、購入した電話番号に電話して、すべてをテストすることができる。エイミーが「ブロックマン・ファミリー・ホットラインへようこそ」というメッセージを読み上げるのが聞こえるはずだ。 http://127.0.0.1:4040/でngrokのデバッグページを開き、どのようなリクエストが来たか、サーバーが正しく応答したかどうかを確認する。

INPUTへの対応

メッセージを変更し、メニューNCCOに入力アクションを追加して、電話をかけてきた人が話したい相手を選べるようにしてみましょう:

@app.route("/incoming/", methods=["POST", "GET"])
def incoming():
    """
    An HTTP endpoint which handles incoming calls.
    """
    return jsonify(
        [
            {
                "action": "talk",
                "text": """
                    Welcome to the Brockman family hotline.
                    To speak to Pete Brockman, please press 1.
                    To speak to Sue Brockman, please press 2.
                """,
                "voiceName": "Amy",
                "bargeIn": True,
            },
            {
                "action": "input",
                "eventUrl": [url_for("family_selection")],
                "maxDigits": 1,
            },
        ]
    )


@app.route("/family-selection/", methods=["POST"])
def family_selection():
    """
    An HTTP endpoint which handles the DTMF input from the main menu.
    """
    postdata = request.json

    if postdata["timed_out"]:
        index = 0
    else:
        index = int(postdata["dtmf"]) - 1
    return jsonify([{
        "action": "talk",
        "text": f"You selected the {index} option",
    }])

まだ呼ぶな!でも、これが何をするものなのか説明しよう:

私はこれを talkアクションに追加した:

"bargeIn": True,

つまり、発信者はアクションがメッセージを読んでいる間に、携帯電話の番号を押すことができる。 talkアクションがまだメッセージを読んでいる間にそれは自動的に次の inputアクションを自動的にアクティブにします。次のアクションも追加しました:

{
    "action": "input",
    "eventUrl": [url_for("family_selection")],
    "maxDigits": 1,
},

この inputアクションはVonageに family_selection関数を呼び出すように指示します。Flaskの url_for関数は指定された関数名のURLを提供します。

のすっきりした使い方partial

url_forデフォルトでは 相対を生成します。相対URLはVonage Voiceではサポートされていません -- そこで、ファイルの先頭に以下を追加しました:

from functools import partial

from flask import url_for as url_for_

# We don't have any use for relative URLs, so hard-code this param:
url_for = partial(url_for_, _external=True)

このpartialの使い方は url_for関数をより便利なものに設定する。 url_for関数のコピーですが _externalパラメータのデフォルト値を True.(に設定されている。 を設定するのを何度も忘れた。を設定するのを忘れていた。)

それをファイルに追加し、サーバーがリロードされたことを確認したら、もう一度自分の番号に電話をかけてみよう。メッセージを読み上げ、電話番号を選ぶことができ、その番号から1を引いた番号を読み上げてくれるはずだ。数秒待つと、「...を選択しました」と表示されるはずだ。 zero.

なぜ入力された数字から1を引いているのですか?それは、リストから人を選ぶためです!上のテストがうまくいったなら、人をリストに入れて、このメニューをもう少しダイナミックにしてみよう。

転送電話

まず本当に必要なのは、"親 "を設定するもっと良い方法だ。電話を転送する相手のリストがあるといい。とりあえず、Pythonファイルにその詳細を定義しておこう。実際の電話番号や公開リポジトリへのコミットはしないでください!

import attr

@attr.s
class Endpoint:
    """ A data class containing a potential callee's details. """
    name = attr.ib()
    phone_number = attr.ib()

ENDPOINTS = [
    Endpoint(name="Pete Brockman", phone_number="447700900123"),
    Endpoint(name="Sue Brockman", phone_number="447700900456"),
]
VONAGE_NUMBER = "447700900847"

上のコードでは、素晴らしい attrsライブラリを使ってシンプルなクラスを定義しています、 Endpointを定義している。そして Endpointのリストを作成している。

次に、アクションを生成するための単純なユーティリティ関数を追加する。 talkアクションを生成する簡単なユーティリティ関数を追加した。これはコードをより読みやすくし、デフォルトで Amyを使うことになります。VonageがサポートしているVoiceのリストは ここで

def talk(message, voice="Amy", barge_in=None):
    """ Utility function to generate a `talk` NCCO action. """
    response = {"action": "talk", "text": message, "voiceName": voice}
    if barge_in is not None:
        response["bargeIn"] = barge_in

    return response

そして 現在メイン・メニューのメッセージを生成するコードを整理した:

def make_answer_message(message, endpoints):
    """
    Generate a script to be read to the caller, informing them of their options.
    """
    endpoint_options = [
        f"To speak to {endpoint.name}, please press {code}."
        for code, endpoint in enumerate(endpoints, start=1)
    ]

    return " ".join([message, *endpoint_options, "If you're not sure, please hold."])

上のコードの最後の行に *endpoint_optionsが見えますか?Python 3では、リストを別のリストに展開することができるのをご存知ですか?この場合、結果的に文字列のリストができあがり、それをスペースでつなげることができます。

これで talkアクションを incomingメソッドのアクションを talk関数に置き換えることができます:

talk(
    make_answer_message(
        "Welcome to the Brockman family hotline.", ENDPOINTS
    ),
    barge_in=True,
),

そして最後に family_selection関数を変更して、実際に電話を電話番号に転送するようにすることもできる:

@app.route("/family-selection/", methods=["POST"])
def family_selection():
    """
    An HTTP endpoint which handles the DTMF input from the main menu.
    """
    postdata = request.json

    try:
        if postdata["timed_out"]:
            index = 0
        else:
            index = int(postdata["dtmf"]) - 1

        if index < len(ENDPOINTS):
            # They elected to speak to an individual
            endpoint = ENDPOINTS[index]
            return jsonify(
                [
                    talk(f"Connecting to {endpoint.name}"),
                    {
                        "action": "connect",
                        "from": VONAGE_NUMBER,
                        "endpoint": [{"type": "phone", "number": endpoint.phone_number}],
                    },
                ]
            )
    except ValueError:
        pass  # This is raised by `int`, and can be ignored - we just forward on to the following error message...

    return jsonify([talk("I didn't understand that option.")])

上のコードは以前よりかなり複雑に見えますが、これはエラーチェックを行い、想定外のキーが押された場合に発信者に通知しているからです。もしユーザーが1か2を押すと、接続中であるというメッセージが読み上げられ、エンドポイントの番号に接続されます。

さて、ここまで何をしてきたか?

  • 学校側は私の番号に電話すれば、接続可能な人のリストを読むことができる。

  • 相手が1か2を押すと、その相手につながる。(ちなみに、電話をかけている側は、お互いに相手の電話番号を見ることができないので、これは通話を匿名化する素晴らしい方法だ!)

  • 発信者が待つと、設定されたリストの最初のエンドポイントに接続される。

ここで止めても構わないが、私には3つ目の条件がある。 第3の条件:緊急時には、発信者が starを押すと、私たち2人に電話がかかってきて、全員を電話会議にかけることができる。

電話会議オプションの追加

電話会議は conversationアクションで作成され、会議参加者がいなくなると(デフォルトで)終了する。電話会議に参加するために必要なのは、最初のアクションで電話会議につけた名前だけです。 conversationアクションでつけた名前です。

クライアントオブジェクトの初期化

親へのアウトバウンドコールを作成するためにVonage Clientオブジェクトが必要になります。このチュートリアルの最初にVonage Pythonライブラリをインストールしたのは幸運でしたね。次の行をファイルの先頭付近に記述してください。もし 必要ならを貼り付けることができます。 application_idprivate_keyの値をファイルに直接貼り付けることもできますが、代わりに環境変数から読み込む方がいいと思います。公開リポジトリにコミットするのは簡単すぎるし、それを持っている人は誰でも あなたのVonage balance!

import vonage

vonage_client = vonage.Client(
    application_id=os.getenv('VONAGE_APPLICATION_ID'),
    private_key=os.getenv('VONAGE_PRIVATE_KEY')
)

ユーザーに'*'を押せることを伝える

その前に、メッセージ "If this is an emergency, please press star."make_answer_message関数が返す文字列にメッセージを追加する。リストの message項目の直後に追加した。

電話会議の開催

では、電話会議を作りましょう。深呼吸してください。それなりに大きなコードの塊です。

という新しいHTTPエンドポイントが必要になる。 conference_nccoと呼ばれる新しいHTTPエンドポイントが必要である。

私は、親への電話発信と呼び出し元へのNCCOアクションを生成するすべてのコードを、次の関数に抽象化した。 create_conference_call.

で生成されるconference_idパラメータが、どのようにURLパスに埋め込まれているかがわかるように、2つの関数を一緒に貼り付けておく。 create_conference_callで生成されるconference_idパラメータが conference_nccoエンドポイントへのURLパスに埋め込まれていることがわかります。 conversationNCCOアクションに埋め込む電話会議の名前を知ることができます。

@app.route("/conference/<conference_id>/ncco", methods=["GET", "POST"])
def conference_ncco(conference_id: str):
    """
    An HTTP endpoint which generates the NCCO actions to connect a callee to
    a conference call.
    """
    return jsonify(
        [
            talk("You are being connected to a family hotline conference call."),
            {"action": "conversation", "name": conference_id},
        ]
    )

def create_conference_call(endpoints):
    """
    Generate an NCCO response to connect the caller to all the provided
    `endpoints` in a single conference call.
    """
    # Generate a unique name for our conference:
    conference_name = str(uuid.uuid4())

    # Loop through the endpoints and dial them into the conference call:
    for endpoint in endpoints:
        vonage_client.create_call(
            {
                "to": [{"type": "phone", "number": endpoint.phone_number}],
                "from": {"type": "phone", "number": VONAGE_NUMBER},
                "answer_url": [
                    url_for("conference_ncco", conference_id=conference_name)
                ],
            }
        )
        print(f"Dialing {endpoint.phone_number} into {conference_name}")

    # Connect the inbound leg to the conference call we're creating:
    return jsonify(
        [
            talk("Connecting all parties."),
            {
                "action": "conversation",
                "name": conference_name,
            },
        ]
    )

スター入力の処理

あとは family_selectiondtmfコードで何をすべきかがわかるように *dtmfコードで何をするのかがわかるように修正する必要がある:

if postdata["dtmf"] == "*":
    return create_conference_call(ENDPOINTS)

テストしてみよう!

Vonageの電話番号に電話をかけ、メインメニューの各指示に従って動作することを確認します!すべてうまくいったら、いよいよ導入です!

配備する!

もし Git リポジトリをチェックすると、環境変数からすべての設定を読み込んだり、Procfileを追加したりといった、ちょっとした変更を加えたことがわかるだろう。これはHerokuへのデプロイが比較的簡単であることを意味するはずだ!Vonageアプリケーションの設定を、開発に使っていたngrok URLの代わりに新しいHeroku URLを指すように更新するのを忘れないでください。

配備が完了したら、お子さんの学校に相談し、連絡先を更新してもらいましょう!

私たちは何をしたのか?

私たちは、学校が私たちに連絡できるようにIVRシステム全体を構築しました!個々の番号に電話を転送することも、全員を1つの電話会議に接続することもできます!

さらなる信用

このサーバーでやろうと考えたことは、他にもいくつかある:

  • この番号に送信されたSMSメッセージを両親の両方に転送する。

  • 学校からの電話に出られなかった場合、保護者に知らせるためのテキストメッセージを送信する。

  • 両方の親に電話をかけ、先に出た方が電話を受ける「レース」コールを作る。2番目の電話は切られ、もう一人の親が対応したというSMSが送られる。

  • 保護者が迎えに来ない場合は、学校にメッセージを録音させ、両方の保護者が受け取れるようにする。

  • 家族のSlackと統合し、電話着信のアラート、SMSメッセージのSlackへの転送、Slack内で再生する録音メッセージの投稿も可能!

ご覧のように、一度始めてしまえば、たくさんの可能性がある。このチュートリアルを楽しんでいただけたなら幸いです。私は です。をフォローしてください!

シェア:

https://a.storyblok.com/f/270183/150x150/a3d03a85fd/placeholder.svg
Mark Smithヴォネージの卒業生

マークはNexmoのクライアント・ライブラリの名目上の責任者である(ただし、彼が書いているのはPythonとJavaのライブラリだけである)。もともとはJavaの開発者だったが、18年間Pythonの開発者であり、最近はGoやRustにも手を出している。プログラミング言語の限界に挑戦し、そのテクニックを他のプログラマーに教えるのが好きだ。バイキングの帽子をかぶっているがバイキングではない。