
Partager:
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.
Comment construire une boîte vocale avec Python et Flask
Temps de lecture : 13 minutes
J'ai décroché le combiné crasseux de la cabine publique et j'ai composé le numéro, comme je l'avais fait une centaine de fois auparavant.
"Ici Oleg's Pizza. Laissez un message après le bip."
C'est tout ce qu'il a dit - il n'y a jamais eu de personne réelle à l'autre bout du fil - juste une voix robotisée provenant d'une entreprise improbable.
[BIP] - [BIP] - [BIP] - [BIP] - [BIP] - quelque part, une cassette a commencé à s'enregistrer. J'ai laissé mon message.
"Bonjour, je m'appelle Chuck. Je voudrais une pizza au pepperoni et aux champignons, s'il vous plaît."
J'ai laissé tomber le combiné et je me suis éloigné.
Je ne m'appelle pas Chuck et je n'aime pas le pepperoni, mais cela ferait passer le message : ma couverture était grillée et, dès lundi, j'aurais disparu, n'étant plus qu'un vague souvenir dans l'esprit de ceux qui me connaissaient.
J'adore les thrillers d'espionnage, et il semble que l'un des aspects les plus difficiles du métier d'espion soit de trouver un numéro mort pour laisser des messages à son supérieur. Heureusement, dans cet article, je vais faciliter la vie de tous les espions en vous montrant comment créer un numéro de téléphone de point mort où vous pouvez laisser des messages à quelqu'un qui les récupérera plus tard sur le Web.
Conditions préalables
Je vais supposer que vous avez lu l'excellent article d'Aaron décrivant comment utiliser Ngrok pour développer des webooks. Si ce n'est pas le cas, lisez-le maintenant - il en vaut la peine.
Je vais également supposer que vous avez une connaissance de base de Python et de Flask.
Je recommande d'installer l'outil outil CLI de Vonage et de lire le court billet de blog sur la façon de l'installer - certaines des instructions ci-dessous l'utiliseront, bien que vous puissiez effectuer ces actions dans le tableau de bord Nexmo si vous préférez.
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.
Ce que vous allez construire
Je vais vous montrer comment créer un service de messagerie vocale de base qui permet aux gens d'appeler votre numéro Nexmo et de laisser un message.
Le message enregistré sera copié sur votre serveur et vous créerez une page web simple qui répertorie les enregistrements et vous permet de les lire dans le navigateur.
Démarrer votre projet
Si vous préférez suivre mon code existant, vous pouvez le trouver icimais je vous recommande de suivre cet article et de le construire vous-même !
La structure de notre dossier de projet ressemble à ceci :

Comme il s'agit d'un petit projet, tout votre code Python ira dans answerphone/__init__.pymais s'il était plus important, vous pourriez le diviser en modules séparés sous le paquetage answerphone package.
Vous placerez également nos ressources statiques sous static et vos modèles dans templates et Flask saura alors où les trouver.
J'ai choisi de sauvegarder mes enregistrements MP3 dans un dossier au niveau du projet. recordings au niveau du projet, en dehors du paquet answerphone car c'est une bonne idée de séparer les données (en particulier les choses téléchargées sur Internet !) du code exécutable.
Vous ne pouvez pas le voir dans l'image ci-dessus, mais il y a aussi un fichier .env dans le répertoire du projet, qui contient toute ma configuration.
Installer les dépendances
Dans mon projet, j'ai utilisé pip-tools pour épingler mes dépendances, mais si vous n'avez jamais utilisé pip-tools, je vous recommande de coller ce qui suit directement dans requirements.txt puis d'exécuter pip install -r requirements.txt:
Un aperçu rapide de nos dépendances :
dotenv sera utilisé pour charger la configuration depuis notre fichier de configuration
.envfichier de configuration.flask est notre framework web et notre serveur web de développement.
tinydb est une base de données très simple qui stocke toutes vos données au format json.
nexmo est la bibliothèque client Nexmo Python, et rend l'utilisation des API Nexmo plus simple que de le faire à la main.
Ouvrez __init__.py dans votre paquetage answerphone et tapez ce qui suit :
from flask import Flask
@app.route("/answer", methods=["GET", "POST"])
def answer():
"""
An NCCO webhook, providing actions that tell Nexmo to read a statement
to the user and then record a message.
"""
return jsonify(
[
{
"action": "talk",
"text": "<speak>You have reached <phoneme alphabet="ipa" ph="əʊlɛgz">Oleg's</phoneme> pizza. Please leave a message after the beep.</speak>",
"voiceName": "Brian",
},
{
"action": "record",
"beepStart": True,
"eventUrl": [ "https://example.com/recording" ],
"endOnSilence": 3,
},
]
)Assurez-vous que vous utilisez Ngrok et démarrez votre serveur de développement avec :
Maintenant, si vous visitez https://your-random-id.ngrok.io/answer avec votre navigateur web, vous devriez voir quelque chose comme ce qui suit :
[
{
"action": "talk",
"text": "You have reached Oleg's pizza. Please leave a message after the beep.",
"voiceName": "Brian"
},
{
"action": "record",
"beepStart": true,
"eventUrl": [
"https://example.com/recording"
],
"endOnSilence": 3
}
]Créons maintenant une application In-App Voice et lions un numéro à cette URL. Dans votre console, lancez l'outil CLI de Vonage qui vous guidera pas à pas dans la création de votre application :
Il imprimera quelque chose comme Application created: 26aa5db4-546a-11e9-8f2d-0f348a273d3aet créera un fichier appelé private.key dans votre répertoire actuel.
Prenez cet identifiant et collez-le dans un nouveau fichier .env comme suit :
Laissez cela pour l'instant - je vous expliquerai comment charger la configuration dans un instant.
Si vous avez besoin d'acheter un numéro, je vous recommande de le faire dans le tableau de bord Nexmo.
Une fois que vous avez acheté un numéro (assurez-vous qu'il est compatible avec Voice !), retournez à votre ligne de commande et utilisez la commande nexmo pour relier le numéro à votre application :
Maintenant, si vous appelez votre numéro Numbers, vous devriez entendre le message dans l'action ci-dessus : "Vous êtes arrivé chez Oleg's pizza. talk ci-dessus : "Vous êtes bien chez Oleg's pizza. Veuillez laisser un message après le bip".
Vérifiez vos journaux Ngrok. Vous remarquerez peut-être des erreurs 404 à /event. Ne vous inquiétez pas pour l'instant - vous ajouterez un webhook d'événement plus tard dans ce tutoriel.
Malheureusement, une fois que Nexmo a terminé l'enregistrement de votre message, il effectue actuellement une requête POST à l'URL de votre action record qui est définie comme https://example.com/recording.
Corrigeons cela pour que vous puissiez recevoir l'événement d'enregistrement et télécharger le MP3, afin que votre gestionnaire puisse récupérer les messages de ses agents.
Dans votre __init__.py, ajoutez ce qui suit :
# Add to your imports:
from dotenv import load_dotenv
from flask import request, url_for
import nexmo
# After your imports:
load_dotenv() # Loads .env config into `os.environ`
client = nexmo.Client(
application_id=os.environ["NEXMO_APPLICATION_ID"],
private_key=os.environ["NEXMO_PRIVATE_KEY"],
)
@app.route("/new-recording", methods=["POST"])
def new_recording():
recording_bytes = client.get_recording(request.json['recording_url'])
recording_id = request.json['recording_uuid']
with open(f"recordings/{recording_id}.mp3", 'wb') as mp3_file:
mp3_file.write(recording_bytes)
return ""et modifiez maintenant votre answer webhook. La deuxième action devrait ressembler à ceci :
{
"action": "record",
"beepStart": True,
"eventUrl": [url_for("new_recording", _external=True)],
"endOnSilence": 3,
},Vous utilisez maintenant la fonction url_for de Flask pour obtenir une URL pointant vers le new_recording que vous venez d'ajouter au fichier.
Assurez-vous que votre dossier recording existe, puis redémarrez le serveur de développement Flask.
Désormais, lorsque vous appelez votre numéro Numbers et que vous laissez un message, vous devriez trouver un fichier MP3 dans le dossier recording dans le dossier Ouvrez-le dans votre lecteur MP3 préféré pour entendre ce qu'il dit !
Si vous le souhaitez, vous pouvez vous arrêter maintenant - vous avez appris toutes les bases de l'enregistrement d'un message par Nexmo, puis du téléchargement de ce message sur votre serveur (Nexmo ne stocke l'enregistrement pour vous que pendant quelques heures).
Mais il serait bon de stocker des métadonnées avec l'audio. il serait judicieux de stocker des métadonnées avec l'audio, afin de connaître l'identité de l'appelant et la date de son appel. De cette façon, vous pouvez ajouter une page répertoriant tous les appels à votre répondeur téléphonique.
J'ai choisi TinyDB pour ce faire - c'est un petit magasin de données très simple qui déverse vos données dans un fichier JSON. Il n'est pas très rapide, et il ne stocke pas très bien beaucoup de données, mais c'est parfait pour ce projet !
Ajoutez ce qui suit à votre fichier .env fichier : DATABASE_PATH=answerphone.db.
Vous indiquez à TinyDB de stocker les données dans ce fichier avec ce qui suit au début de votre fichier __init__.py de votre fichier :
from tinydb import TinyDB, Query
db = TinyDB(os.environ["DATABASE_PATH"])Ajoutez maintenant les éléments suivants, afin de créer deux "tables" pour stocker les données relatives à l'appelant et les données relatives à l'enregistrement :
calls = db.table('calls')
recordings = db.table('recordings')Vous devez maintenant faire deux choses : Vous devez répondre aux événements d'appel et enregistrer les données d'appel lorsqu'un appel est pris ; et vous devez ajouter quelques lignes à votre recording pour qu'il stocke les données d'enregistrement dans la base de données.
Tout d'abord, ajoutez le fichier event webhook :
@app.route("/event", methods=["POST"])
def event():
if request.json.get('status') == 'answered':
calls.insert(request.json)
return ""La ligne calls.insert(request.json) stocke toutes les données JSON de la requête dans le tableau calls que vous avez créée ci-dessus.
Maintenant, ajoutez une ligne similaire à votre recording après le code d'enregistrement du fichier MP3 dans votre dossier recordings dossier :
...
with open(f"recordings/{recording_id}.mp3", 'wb') as mp3_file:
mp3_file.write(recording_bytes)
recordings.insert(request.json)
return ""Appelez à nouveau votre numéro Numbers et laissez un message. Vérifiez qu'il fonctionne sans erreur.
Si vous jetez un coup d'œil à l'intérieur de answerphone.db vous devriez voir une charge de données JSON stockées. Maintenant, chargeons ces données dans une belle page web !
Tout d'abord, ajoutez une vue qui vous permettra de charger un fichier MP3 dans le navigateur :
@app.route("/recordings/<uuid>")
def recording(uuid):
response = make_response(open(f'recordings/{uuid}.mp3', 'rb').read())
response.headers['Content-Type'] = 'audio/mpeg'
return responseLe code ci-dessus ouvre le fichier MP3 binaire, crée une réponse à partir des octets, puis définit l'en-tête content-type à "audio/mpeg", qui est le type correct pour les données MP3.
Vous pouvez tester cela en chargeant l'URL "/recordings/you-uuid-goes-here" en utilisant l'identifiant d'un des fichiers MP3 de votre dossier d'enregistrements.
Vous devez maintenant ajouter une vue qui répertorie tous les enregistrements, ainsi que certaines des données d'appel associées à chaque enregistrement.
Cette tâche peut être facilitée par la présence d'une petite classe d'assistants.
Placez ce code au début de votre fichier __init__.py fichier :
class Recording:
def __init__(self, data):
self.uuid = data['recording_uuid']
related_calls = calls.search(Query().conversation_uuid == data['conversation_uuid'])
if related_calls:
self.related_call = related_calls[0]
else:
self.related_call = NoneCette classe est conçue pour être initialisée à l'aide des données JSON fournies au point de terminaison recording et stockées dans la table recordings dans notre base de données.
Il recherche automatiquement les données d'appel associées dans le tableau et les ajoute à l'objet Enregistrement en tant que données d'appel. calls et les ajoute à l'objet Enregistrement en tant qu'attribut related_call attribut.
Ecrivez maintenant le code de vue suivant, qui transmet à la vue une instance Recording à la vue pour chaque enregistrement stocké dans la base de données :
@app.route("/")
def index():
"""
A view which lists all stored recordings.
"""
return render_template("index.html.j2", recordings=[Recording(r) for r in recordings])Cette opération échoue pour l'instant, car vous n'avez pas créé de fichier modèle !
Créez un fichier à l'adresse answerphone/templates/index.html.j2 et mettez-y quelque chose comme ce qui suit :
<!doctype html>
<html>
<head>
<title>Oleg's Pizza</title>
</head>
<body>
<h1><i>"Oleg's Pizza"</i><br>Dead Drop Recordings</h1>
{% for recording in recordings -%}
<h2>Call From: <em>{{ recording.related_call.from }}</em></h2>
<p><strong>When:</strong> {{ recording.related_call.timestamp }}</p>
<a href="/recordings/{{ recording.uuid }}">Listen</a>
{% endfor -%}
</body>
</html>Maintenant, si vous visitez votre https://localhost:5000/ vous devriez voir quelque chose comme ce qui suit :

Vous êtes désormais un maître de l'espionnage !
Je vais résumer ce que vous venez de faire :
Vous avez répondu à un appel téléphonique entrant par des actions NCCO.
Vous avez demandé à Nexmo d'enregistrer une partie d'un appel téléphonique
Vous avez géré l'événement d'enregistrement pour télécharger le fichier MP3 créé.
Vous avez stocké les données d'appel dans une base de données et créé une liste de lecture consultable sur le web !
Plus d'informations
Si vous souhaitez approfondir ce que vous venez d'apprendre, ce qui suit peut vous être utile :
Consultez également le GitHub Repo pour ce projet, car j'ai documenté le code et amélioré la vue de la liste.
Prochaines étapes
Il y a plusieurs façons d'aller plus loin dans ce projet. Vous pourriez utiliser un websocket pour avertir le navigateur lorsqu'un nouvel enregistrement apparaît, de sorte que le gestionnaire n'ait pas à recharger le navigateur pour recevoir des messages de son agent.
Vous pouvez également utiliser l'API SMS de Nexmo de Nexmo pour envoyer au gestionnaire un SMS lorsqu'un nouvel enregistrement est disponible !
Si vous fabriquez quelque chose de cool, envoyez-nous un courriel à l'adresse suivante devrel@nexmo.com pour nous le faire savoir !
Partager:
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.
