https://d226lax1qjow5r.cloudfront.net/blog/blogposts/build-a-family-hotline-dr/Blog_Family-Hotline_1200x600.png

Créer une ligne d'assistance téléphonique pour les familles avec Vonage

Publié le May 4, 2021

Temps de lecture : 16 minutes

Toutes les entreprises que j'appelle ont toujours un système de réponse automatisé qui me pose des questions avant que je puisse parler à une vraie personne. I avoir un de ces systèmes ?"

Ma fille est entrée à l'école secondaire il y a quelques mois, et le formulaire qu'ils nous ont envoyé à remplir n'avait qu'un seul emplacement pour le numéro de téléphone de la personne à contacter. un seul emplacement pour un numéro de téléphone de contact. Que se passera-t-il si nous leur donnons mes coordonnées et que je ne suis pas disponible ? Heureusement pour moi, je travaille pour Vonage et je sais comment configurer un numéro de téléphone capable de renvoyer les appels vers l'un ou l'autre d'entre nous - ou les deux ! Vonage à la rescousse !

Ce que nous allons construire

  1. L'école reçoit un numéro Vonage comme numéro de contact.

  2. S'ils appellent le numéro, un message automatique leur propose les options suivantes :

    1. Indiquez les noms des parents, avec un chiffre à appuyer pour être transmis à chacun d'entre eux.

    2. Si l'appelant ne fait rien, transmettre au premier parent de la liste.

    3. Appuyez sur '*' en cas d'urgence. Cela permet d'établir une conférence téléphonique et d'appeler les deux parents.

J'avais encore quelques exigences :

  1. Être simple et pratiquement gratuit à héberger et à gérer.

  2. Pas de base de données à gérer, pour plus de simplicité.

Comment nous allons le construire

J'ai choisi deux technologies avec lesquelles je suis le plus familier : Python 3.6 et Flask. J'ai choisi Python 3.6 parce que c'est la version qui a été livrée avec f-strings et qu'elles sont géniales ! J'utilise également pipenv pour gérer mes dépendances Python.

Cette combinaison nous permet de construire un excellent système IVR (Interactive Voice Response) à domicile en un peu plus de 100 lignes de code !

Vonage API Account

To complete this tutorial, you will need a Vonage API account. If you don’t have one already, you can sign up today and start building with free credit. Once you have an account, you can find your API Key and API Secret at the top of the Vonage API Dashboard.

C'est parti !

Commençons par mettre en place notre environnement de développement.

Parce que Vonage doit appeler le serveur que vous allez écrire, et que votre ordinateur portable se trouve derrière un bon pare-feu sécurisé, vous voudrez probablement utiliser ngrok pour le développement local - permettez-moi de vous recommander l'excellent article de blog de mon collègue Aaron sur l'utilisation de ngrok de mon collègue Aaron sur l'utilisation de ngrok.

Vous voudrez probablement installer pipenv globalement, et à partir de là, nous l'utiliserons pour gérer l'environnement virtuel de notre projet :

sudo pip install --upgrade pipenv

Installons maintenant les dépendances dont nous aurons besoin pour ce projet et activons notre environnement virtuel. Exécutez ces commandes dans le répertoire de votre nouveau projet - j'ai appelé le mien 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

Commençons maintenant à écrire notre serveur Flask.

Ouvrez un fichier appelé hotline.pyet tapez ce qui suit :

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",
            }
        ]
    )

Maintenant, dans des fenêtres de terminal séparées vous devez exécuter les commandes suivantes en même temps :

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

Lorsque vous exécutez ngrokil affiche le nom de domaine généré qu'il transmettra à votre serveur. Cela ressemblera à quelque chose comme ceci : https://abcde1234.ngrok.io -> localhost:5000. Vous pouvez maintenant tester votre application Flask à l'adresse suivante https://abcde1234.ngrok.io/incoming (remplacez abcde1234 par ce qui a été imprimé sur votre terminal).

Vous devriez voir quelque chose comme ceci :

First NCCO testFirst Test

Passer un appel téléphonique à votre serveur

Vous devez maintenant configurer un numéro virtuel Vonage pour appeler votre serveur lorsque quelqu'un appelle votre numéro. Vous avez donc besoin de 3 choses :

  1. A Account Vonage

  2. Une application Voice configurée

  3. Un numéro virtuel Vonage configuré.

Créer un Account Vonage

Vonage API Account

To complete this tutorial, you will need a Vonage API account. If you don’t have one already, you can sign up today and start building with free credit. Once you have an account, you can find your API Key and API Secret at the top of the Vonage API Dashboard.

This tutorial also uses a virtual phone number. To purchase one, go to Numbers > Buy Numbers and search for one that meets your needs.

Installer le CLI de Vonage

Installez le CLI de Vonage globalement avec cette commande :

npm install @vonage/cli -g

Ensuite, configurez le CLI avec votre clé et votre secret API Vonage. Vous pouvez trouver ces informations dans le tableau de bord du développeur.

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

Créer une application Voice

Créez un nouveau répertoire pour votre projet et placez-y un CD :

mkdir my_project
CD my_project

Maintenant, utilisez le CLI pour créer une application 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

Vous voudrez sauvegarder l'identifiant qui est imprimé après l'opération. Application created:. Vous en aurez besoin à l'étape suivante.

Acheter un Numbers et le lier à votre In Voice-App Voice

Vous avez maintenant besoin d'un numéro pour pouvoir recevoir des appels. Vous pouvez en louer un en utilisant la commande suivante (en remplaçant le code du pays par votre code). Par exemple, si vous êtes aux États-Unis, remplacez GB par US:

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

Reliez maintenant le numéro à votre application :

vonage apps:link --number=VONAGE_NUMBER APP_ID

Ok!

Tu peux maintenant tester tout cela en appelant le numéro de téléphone que tu viens d'acheter. Si vous entendez un message indiquant que le numéro n'a pas été reconnu, ou si les choses ne se passent pas comme prévu, ouvrez la page de débogage de ngrok à l'adresse suivante http://127.0.0.1:4040/ dans votre navigateur pour voir quelles requêtes ont été envoyées et si le serveur a répondu correctement.

Répondre à INPUT

Modifions le message et ajoutons une action de saisie à notre menu NCCO afin que l'appelant puisse sélectionner la personne à laquelle il souhaite parler :

@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",
    }])

Ne l'appelez pas encore ! Nous ne sommes pas tout à fait prêts... mais laissez-moi vous expliquer ce que cela fait :

J'ai ajouté ceci à l'action talk action :

"bargeIn": True,

Cela signifie que l'appelant peut appuyer sur un numéro de son téléphone pendant que l'action est encore en train de lui lire le message. talk est encore en train de lui lire le message. Cela activera automatiquement l'action suivante. input action suivante. J'ai également ajouté l'action suivante :

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

Cette action input indique à Vonage d'appeler la fonction family_selection que nous venons d'ajouter. La fonction url_for de Flask fournit l'URL pour le nom de la fonction donnée.

Une utilisation judicieuse departial

url_for génère par défaut un relative URL RELATIVE. Les URL relatives ne sont pas prises en charge par Vonage Voice - j'ai donc ajouté ce qui suit au début du fichier :

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)

Cette utilisation de partial fait de la fonction url_for à quelque chose de plus utile - il s'agit d'une copie de la fonction url_for de Flask, mais avec la valeur par défaut du paramètre _external est fixée à True. (J'ai oublié de définir ce paramètre tant de fois avant d'y remédier).

Une fois que vous avez ajouté cela à votre fichier et que vous vous êtes assuré que votre serveur a été rechargé, il est temps de rappeler votre numéro. Il devrait vous lire le message, vous permettre de choisir un numéro sur votre téléphone, puis vous lire ce numéro avec un soustrait. Si vous attendez quelques secondes, il devrait vous indiquer que vous avez sélectionné zero.

Pourquoi est-ce que je soustrais 1 au nombre qu'ils ont entré ? C'est pour pouvoir sélectionner une personne dans une liste ! Si le test ci-dessus s'est bien déroulé, plaçons nos personnes dans une liste et rendons ce menu un peu plus dynamique.

Renvoi d'appels

La première chose dont nous avons vraiment besoin est une meilleure façon de configurer nos "parents". C'est bien d'avoir une liste des personnes à qui nous voulons transférer les appels. Pour l'instant, définissons leurs coordonnées dans le fichier Python. S'il vous plaît, ne faites pas cela avec de vrais numéros de téléphone et ne le publiez pas dans un dépôt public !

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"

Dans le code ci-dessus, j'utilise l'étonnante fonction attrs pour définir une classe simple, Endpointpour contenir le nom et le numéro de téléphone d'une personne. Je crée ensuite une liste de Endpointpour les personnes vers lesquelles nous pourrions vouloir transférer des appels.

J'ajoute maintenant une fonction d'utilité simple pour générer des talk d'actions. Cela rendra le code plus lisible et signifie que, par défaut, nous utiliserons la fonction Amy Voice. Vous pouvez obtenir une liste de toutes les voix prises en charge par Voice ici

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

Et maintenant J'ai intégré le code pour générer le message du menu principal :

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."])

Vous voyez cela *endpoint_options dans la dernière ligne du code ci-dessus ? Saviez-vous que vous pouvez faire cela en Python 3 pour développer une liste dans une autre liste ? Dans ce cas, nous obtenons une liste de chaînes de caractères que nous pouvons relier par des espaces.

Vous pouvez maintenant remplacer l'action talk dans la méthode incoming par l'appel suivant à la fonction talk par l'appel suivant à la fonction

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

Enfin, vous pouvez modifier la fonction family_selection pour qu'elle transfère effectivement l'appel vers un numéro de téléphone :

@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.")])

Le code ci-dessus semble beaucoup plus compliqué qu'auparavant, mais c'est parce que nous vérifions maintenant les erreurs et indiquons à l'appelant s'il a appuyé sur une touche à laquelle nous ne nous attendions pas. Si l'utilisateur appuie sur 1 ou 2, il recevra un message lui indiquant qu'il est connecté, puis il sera connecté au numéro du point d'extrémité.

Bon, qu'avons-nous fait jusqu'à présent ?

  • L'école peut appeler mon numéro et obtenir une liste de personnes avec lesquelles elle peut entrer en contact.

  • S'il appuie sur 1 ou 2, il sera mis en relation avec cette personne. (Par ailleurs, aucun des deux interlocuteurs ne peut voir le numéro de téléphone de l'autre, ce qui constitue un excellent moyen de rendre les appels anonymes).

  • Si l'appelant attend, il sera connecté au premier point d'extrémité de la liste configurée.

Nous pourrions nous arrêter là si nous le souhaitions, mais j'avais une troisième exigence. troisième exigence: En cas d'urgence, l'appelant pouvait appuyer sur starpour nous appeler tous les deux et nous faire participer à une conférence téléphonique.

Ajouter une option de conférence téléphonique

Une conférence téléphonique est créée par une action conversation et se termine (par défaut) lorsqu'il n'y a plus de participants. La seule chose dont nous avons besoin pour connecter une personne à une conférence téléphonique est le nom que nous avons donné à la conférence téléphonique lors de la première action. conversation action.

Initialiser un objet client

Nous allons avoir besoin d'un objet Vonage Client pour créer des appels sortants vers les parents. Heureusement que nous avons installé la bibliothèque Python de Vonage au début de ce tutoriel, n'est-ce pas ? Placez les lignes suivantes au début de votre fichier. Si vous voulez vous pouvez coller vos application_id et private_key directement dans le fichier, mais je pense qu'il est préférable de les charger à partir des variables d'environnement. Il est trop facile de les placer dans un dépôt public, et quiconque les possède peut utiliser votre Vonage !

import vonage

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

Indiquer à l'utilisateur qu'il peut appuyer sur "*".

Avant cela, ajoutez le message "If this is an emergency, please press star." à la chaîne renvoyée par notre make_answer_message . Je l'ai ajouté juste après l'élément message dans la liste.

Créer une conférence téléphonique

Créons maintenant notre conférence téléphonique. Respirez profondément ; il s'agit d'un bloc de code assez volumineux.

Nous aurons besoin d'un nouveau point de terminaison HTTP, appelé conference_nccoqui fournira des actions NCCO pour chacun des parents participant à la conférence téléphonique.

J'ai regroupé tout le code permettant d'appeler les parents et de générer des actions NCCO pour l'appelant dans une fonction appelée create_conference_call.

Je colle les deux fonctions ensemble pour que vous puissiez voir comment le paramètre conference_id, qui est généré dans la fonction create_conference_call est intégré dans le chemin d'accès à l'URL du point de terminaison conference_ncco afin de connaître le nom de la conférence téléphonique à intégrer dans l'action du conversation NCCO.

@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,
            },
        ]
    )

Gestion d'une entrée en étoile

Il ne nous reste plus qu'à modifier family_selection pour qu'elle sache quoi faire avec un code * dtmf, en plaçant ceci au début de la fonction :

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

Testez-le !

Appelez votre numéro Vonage et suivez chacune des instructions du menu principal pour vérifier qu'elles fonctionnent ! Si tout fonctionne, il est temps de le déployer !

Déployez-le !

Si vous consultez le dépôt dépôt Git pour ce projet, vous verrez que je lui ai apporté quelques modifications mineures, notamment le chargement de toute la configuration à partir des variables d'environnement, et l'ajout d'un Procfile. Cela devrait signifier qu'il est relativement simple de le déployer sur Heroku - mais je vous laisse faire l'exercice ! N'oubliez pas de mettre à jour la configuration de votre Applications Vonage pour qu'elle pointe vers la nouvelle URL Heroku au lieu de l'URL ngrok que vous avez utilisée pour le développement.

Une fois qu'il a été déployé, il est temps de contacter l'école de votre enfant et de lui demander de mettre à jour vos coordonnées !

Qu'avons-nous fait ?

Nous avons construit un système IVR complet pour permettre à l'école de nous contacter ! Il peut renvoyer les appels vers des numéros individuels, ou connecter tout le monde en une seule conférence téléphonique !

Autres crédits

Voici d'autres choses que j'ai envisagé de faire avec ce serveur :

  • Renvoi aux deux parents des SMS envoyés au numéro.

  • L'envoi de messages textuels pour informer un parent s'il a manqué un appel de l'école.

  • Créer un appel de "course" qui appellerait les deux parents et le premier qui répondrait prendrait l'appel. Le deuxième appel serait coupé et recevrait un SMS indiquant que l'autre parent l'a pris en charge.

  • Si aucun parent ne décroche, l'école peut enregistrer un message, qui sera alors disponible pour les deux parents.

  • Intégration à un Slack familial, avec des alertes en cas d'appels téléphoniques entrants, le transfert de SMS vers le Slack, et même la publication de messages enregistrés à diffuser au sein du Slack !

Comme vous pouvez le voir, il y a beaucoup de possibilités une fois que vous commencez. J'espère que vous avez pris du plaisir à suivre ce tutoriel. Je suis @judy2k sur Twitter - suivez-moi ou posez-moi des questions sur ce tutoriel !

Partager:

https://a.storyblok.com/f/270183/150x150/a3d03a85fd/placeholder.svg
Mark SmithAnciens de Vonage

Mark était nominalement responsable des bibliothèques clients de Nexmo (bien qu'il n'écrive que les bibliothèques Python et Java). Développeur Java à l'origine, il développe Python depuis 18 ans et s'essaie de plus en plus à Go et Rust. Il aime repousser les limites des langages de programmation et enseigner ces techniques à d'autres programmeurs. Il porte un chapeau de viking, mais n'est pas un viking, et il est Judy2k sur Twitter pour des raisons dont il ne parlera pas.