
Compartir:
Mark era nominalmente responsable de las bibliotecas cliente de Nexmo (aunque sólo escribe las bibliotecas Python y Java). Originalmente era un desarrollador Java, ha sido un desarrollador Python durante 18 años, y está incursionando cada vez más en Go y Rust. Le gusta llevar al límite los lenguajes de programación y luego enseñar estas técnicas a otros programadores. Tiene un sombrero vikingo, pero no es un vikingo, y es Judy2k en Twitter por razones que no discutirá.
Cómo construir un buzón de voz con Python y Flask
Tiempo de lectura: 12 minutos
Levanté el mugriento auricular del teléfono público y marqué el número, como había hecho cientos de veces antes.
"Soy Oleg's Pizza. Deje un mensaje después de la señal".
Eso era todo lo que decía: nunca había una persona real al otro lado de la línea, sólo una voz robótica de una empresa inverosímil.
[BEEP] - en algún lugar una cinta comenzó a grabar. Dejé mi mensaje.
"Hola, soy Chuck. Quiero una pizza de pepperoni y champiñones, por favor".
Solté el auricular y me alejé.
No me llamo Chuck y no me gusta el salchichón, pero esto transmitiría el mensaje: me habían descubierto y el lunes ya no sería más que un recuerdo borroso en la mente de los que me conocían.
Me encanta un buen thriller de espías, y parece que una de las partes más difíciles de ser un espía es encontrar un dead-drop para dejar mensajes para su controlador. Afortunadamente, en este post, voy a hacer la vida más fácil para todos ustedes espías por ahí mostrando cómo hacer un número de teléfono dead-drop donde se puede dejar mensajes a alguien para recoger más tarde en la Web.
Requisitos previos
Voy a asumir que has leído el impresionante post de Aaron que describe cómo usar Ngrok para desarrollar webooks. Si no lo has hecho ve a leerlo ahora - vale la pena.
También voy a suponer que tienes conocimientos básicos de Python y Flask.
Recomiendo instalar la herramienta CLI de Vonage y leer la breve publicación del blog sobre cómo instalarla; algunas de las instrucciones a continuación la utilizarán, aunque puedes completar estas acciones en el Panel de Nexmo si lo prefieres.
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.
Qué vas a construir
Te voy a mostrar cómo construir un servicio básico de correo de voz que permite a la gente llamar a su número Nexmo y dejar un mensaje.
El mensaje grabado se copiará en tu servidor y crearás una página web sencilla que enumere las grabaciones y permita reproducirlas en el navegador.
Inicio del proyecto
Si prefiere seguir con mi código existente, puede encontrarlo aquípero te recomiendo que sigas este post y lo construyas tú mismo.
La estructura de la carpeta de nuestro proyecto es la siguiente:

Como este es un proyecto pequeño, todo tu código Python irá en answerphone/__init__.pypero si fuera más grande, podrías dividirlo en módulos separados bajo el paquete answerphone paquete.
También pondrás nuestros recursos estáticos en static y tus plantillas en templates y Flask sabrá dónde encontrarlos.
He optado por guardar mis grabaciones MP3 en una carpeta a nivel de proyecto recordings fuera del paquete answerphone porque es una buena idea separar los datos (¡especialmente los que se descargan de Internet!) del código ejecutable.
No se puede ver en la imagen de arriba, pero también hay un archivo .env en el directorio del proyecto, que contiene toda mi configuración.
Instalar dependencias
En mi proyecto, he utilizado pip-tools para fijar mis dependencias, pero si no has usado pip-tools antes, te recomiendo que pegues lo siguiente directamente en requirements.txt y luego ejecute pip install -r requirements.txt:
Un rápido resumen de nuestras dependencias:
dotenv se utilizará para cargar la configuración de nuestro
.envarchivo de configuración.matraz es nuestro framework web y servidor web de desarrollo.
tinydb es una base de datos realmente simple que almacena todos tus datos como json.
nexmo es la librería cliente Python de Nexmo, y hace que usar las APIs de Nexmo sea más sencillo que hacerlo a mano.
Abra __init__.py en su answerphone paquete y escriba lo siguiente:
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,
},
]
)Asegúrese de que está ejecutando Ngrok e inicie su servidor de desarrollo con:
Ahora, si visita https://your-random-id.ngrok.io/answer con tu navegador web deberías ver algo como lo siguiente:
[
{
"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
}
]Ahora vamos a crear una aplicación Voice y vincular un número a esta URL. En tu consola, ejecuta la herramienta CLI de Vonage que te guiará paso a paso para crear tu aplicación:
Imprimirá algo como Application created: 26aa5db4-546a-11e9-8f2d-0f348a273d3ay creará un archivo llamado private.key en su directorio actual.
Tome este ID y péguelo en un nuevo .env así:
Deja esto por ahora - voy a explicar cómo cargar la configuración en un momento.
Si necesitas comprar un número, te recomiendo que lo hagas en el Panel Nexmo.
Una vez que hayas comprado un número (asegúrate de que es compatible con Voice), vuelve a la línea de comandos y utiliza el comando nexmo para vincular el número a tu aplicación:
Ahora, si llama a su número Nexmo, debería escuchar el mensaje de la talk acción anterior: "Ha llamado a la pizza de Oleg. Por favor, deje un mensaje después de la señal." ¡Vale!
Compruebe sus registros de Ngrok. Usted puede notar algunos errores 404 a /event. No te preocupes por esto ahora - añadirás un webhook de eventos más adelante en este tutorial.
Desafortunadamente, una vez que Nexmo ha terminado de grabar su mensaje, actualmente está haciendo una solicitud POST a la URL en su record que está configurada como https://example.com/recording.
Vamos a arreglar eso para que pueda recibir el evento de grabación y descargar el MP3, para que su manejador pueda recoger los mensajes de sus agentes.
En su __init__.pyañada lo siguiente:
# 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 ""y ahora modifique su answer webhook. La segunda acción debe tener este aspecto:
{
"action": "record",
"beepStart": True,
"eventUrl": [url_for("new_recording", _external=True)],
"endOnSilence": 3,
},Ahora estás utilizando la función url_for de Flask para obtener una URL que apunte al new_recording que acabas de añadir al archivo.
Asegúrese de que su carpeta recording y reinicie el servidor de desarrollo Flask.
Ahora, cuando llame a su número Nexmo y deje un mensaje, debería encontrar un archivo MP3 en la carpeta recording carpeta. Ábrelo en tu reproductor MP3 favorito para escuchar lo que dice.
Si lo desea, puede detenerse ahora: ya ha aprendido todo lo básico sobre cómo hacer que Nexmo grabe un mensaje y, a continuación, cómo descargar ese mensaje en su servidor (Nexmo sólo almacena la grabación para usted durante unas horas).
Pero sería una buena idea almacenar algunos metadatos junto con el audio, para saber quién era la persona que llamaba y cuándo lo hizo. De esta forma, puedes añadir una página con una lista de todas las llamadas a tu contestador automático.
Elegí TinyDB es un pequeño almacén de datos realmente sencillo que vuelca los datos a un archivo JSON. No es muy rápido, y no almacenará muchos datos muy bien, ¡pero está bien para este proyecto!
Añada lo siguiente a su archivo .env archivo: DATABASE_PATH=answerphone.db.
Le dices a TinyDB que almacene los datos en este archivo con lo siguiente cerca de la parte superior de tu archivo __init__.py archivo:
from tinydb import TinyDB, Query
db = TinyDB(os.environ["DATABASE_PATH"])Ahora añada lo siguiente, para crear dos "tablas" para almacenar los datos de la persona que llama y los datos de la grabación:
calls = db.table('calls')
recordings = db.table('recordings')Ahora tienes que hacer dos cosas: Necesitas responder a los eventos de llamada y grabar los datos de la llamada cuando una llamada es contestada; y necesitas añadir un par de líneas a tu recording webhook para que almacene los datos de grabación en la base de datos.
En primer lugar, añada el event webhook:
@app.route("/event", methods=["POST"])
def event():
if request.json.get('status') == 'answered':
calls.insert(request.json)
return ""La línea calls.insert(request.json) almacena todos los datos JSON de la solicitud en la tabla calls que creó anteriormente.
Ahora, añade una línea similar a tu recording webhook, después del código para guardar el archivo MP3 en la carpeta recordings carpeta:
...
with open(f"recordings/{recording_id}.mp3", 'wb') as mp3_file:
mp3_file.write(recording_bytes)
recordings.insert(request.json)
return ""Vuelva a llamar a su número Nexmo y deje un mensaje. Compruebe que funciona sin errores.
Si echas un vistazo dentro de answerphone.db deberías ver una carga de datos JSON almacenados. ¡Ahora vamos a cargar esos datos en una bonita página web!
En primer lugar, añada una vista que le permita cargar un archivo MP3 en el navegador:
@app.route("/recordings/<uuid>")
def recording(uuid):
response = make_response(open(f'recordings/{uuid}.mp3', 'rb').read())
response.headers['Content-Type'] = 'audio/mpeg'
return responseEl código anterior abre el archivo binario MP3, crea una respuesta a partir de los bytes y, a continuación, establece la cabecera content-type en 'audio/mpeg', que es el tipo correcto para los datos MP3.
Puedes probarlo cargando la URL "/recordings/you-uuid-goes-here" con el id de uno de los archivos MP3 de tu carpeta de grabaciones.
Ahora debe añadir una vista que enumere todas las grabaciones, junto con algunos de los datos de llamada asociados a cada grabación.
Esto puede facilitarse con una pequeña clase de ayudantes.
Ponga este código cerca de la parte superior de su __init__.py archivo:
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 = NoneEsta clase está diseñada para inicializarse utilizando los datos JSON proporcionados al recording y almacenados en la tabla recordings de nuestra base de datos.
Busca automáticamente los datos de llamada asociados en la tabla calls y los añade al objeto Recording como el atributo related_call atributo.
Ahora escribe el siguiente código de vista, que pasa una Recording a la vista por cada grabación almacenada en la base de datos:
@app.route("/")
def index():
"""
A view which lists all stored recordings.
"""
return render_template("index.html.j2", recordings=[Recording(r) for r in recordings])Esto fallará por el momento, ¡porque no has creado un archivo de plantilla!
Cree un archivo en answerphone/templates/index.html.j2 y pon dentro algo como lo siguiente
<!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>Ahora, si visita su https://localhost:5000/ debería ver algo como lo siguiente:

¡Ahora eres un maestro espía!
Resumiré lo que acabas de hacer:
Has respondido a una llamada telefónica entrante con algunas acciones de la OCN.
Usted ordenó a Nexmo grabar parte de una llamada telefónica
Ha gestionado el evento de grabación para descargar el archivo MP3 creado.
Has almacenado los datos de las llamadas en una base de datos y has creado una lista de reproducción navegable por Internet.
Más información
Si quieres profundizar un poco más en lo que acabas de aprender, lo siguiente puede resultarte útil:
Además, consulte el GitHub Repo para este proyecto, ya que he documentado el código y mejorado la vista de lista.
Próximos pasos
Hay algunas maneras de llevar este proyecto más lejos. Podrías utilizar un websocket para notificar al navegador cuando aparezca una nueva grabación, de forma que el manejador no tenga que recargar el navegador para recibir mensajes de su agente.
También puede utilizar SMS API de Nexmo de Nexmo para enviar un SMS cuando haya una nueva grabación disponible.
Si haces algo chulo, envíanos un correo electrónico a devrel@nexmo.com para contárnoslo.
Compartir:
Mark era nominalmente responsable de las bibliotecas cliente de Nexmo (aunque sólo escribe las bibliotecas Python y Java). Originalmente era un desarrollador Java, ha sido un desarrollador Python durante 18 años, y está incursionando cada vez más en Go y Rust. Le gusta llevar al límite los lenguajes de programación y luego enseñar estas técnicas a otros programadores. Tiene un sombrero vikingo, pero no es un vikingo, y es Judy2k en Twitter por razones que no discutirá.
