https://d226lax1qjow5r.cloudfront.net/blog/blogposts/how-to-build-a-voicemail-with-python-flask-dr/python-dead-drop.png

Wie man eine Voicemail Dead-Drop mit Python und Flask bauen

Zuletzt aktualisiert am May 13, 2021

Lesedauer: 12 Minuten

Ich nahm den schmuddeligen Hörer des öffentlichen Münztelefons in die Hand und wählte die Nummer, wie ich es schon hundertmal zuvor getan hatte.

"Hier ist Oleg's Pizza. Hinterlassen Sie eine Nachricht nach dem Signalton."

Das war alles, was es zu sagen gab - es war nie ein echter Mensch am anderen Ende der Leitung - nur eine Roboterstimme von einem unwahrscheinlichen Unternehmen.

[BEEP] - Irgendwo begann ein Band aufzuzeichnen. Ich habe meine Nachricht hinterlassen.

"Hallo, hier ist Chuck. Ich hätte gerne eine Pizza mit Salami und Pilzen."

Ich ließ den Hörer fallen und ging weg.

Ich heiße nicht Chuck, und ich mag keine Peperoni, aber das würde die Botschaft vermitteln: Meine Tarnung war aufgeflogen, und am Montag würde ich verschwunden sein, nur eine verblassende Erinnerung in den Köpfen derer, die mich kannten.


Ich liebe gute Spionagethriller, und es scheint, dass eine der schwierigsten Aufgaben eines Spions darin besteht, eine tote Nummer zu finden, auf der man Nachrichten für seinen Kontaktmann hinterlassen kann. Glücklicherweise werde ich in diesem Beitrag allen Spionen da draußen das Leben erleichtern, indem ich Ihnen zeige, wie Sie eine tote Telefonnummer einrichten, unter der Sie Nachrichten für jemanden hinterlassen können, der sie später im Internet abholt.

Voraussetzungen

Ich gehe davon aus, dass Sie Aarons großartigen Beitrag gelesen haben, der beschreibt, wie man Ngrok für die Entwicklung von Webooks. Wenn nicht, dann lesen Sie ihn jetzt - es lohnt sich.

Ich gehe auch davon aus, dass Sie über Grundkenntnisse in Python und Flask verfügen.

Ich empfehle die Installation des Vonage CLI-Tool und die Lektüre des kurzen Blog-Beitrag über die Installation des Tools zu lesen - einige der folgenden Anweisungen verwenden es, obwohl Sie diese Aktionen auch im Nexmo Dashboard durchführen können, wenn Sie dies bevorzugen.

Vonage API-Konto

Um dieses Tutorial durchzuführen, benötigen Sie ein Vonage API-Konto. Wenn Sie noch keines haben, können Sie sich noch heute anmelden und mit einem kostenlosen Guthaben beginnen. Sobald Sie ein Konto haben, finden Sie Ihren API-Schlüssel und Ihr API-Geheimnis oben auf dem Vonage-API-Dashboard.

In diesem Lernprogramm wird auch eine virtuelle Telefonnummer verwendet. Um eine zu erwerben, gehen Sie zu Rufnummern > Rufnummern kaufen und suchen Sie nach einer Nummer, die Ihren Anforderungen entspricht.

Was Sie bauen werden

Ich zeige Ihnen, wie Sie einen einfachen Voicemail-Dienst einrichten, mit dem Anrufer Ihre Nexmo-Nummer anrufen und eine Nachricht hinterlassen können.

Die aufgezeichnete Nachricht wird auf Ihren Server kopiert, und Sie erstellen eine einfache Webseite, auf der die Aufnahmen aufgelistet sind und die Sie im Browser abspielen können.

Ihr Projekt beginnen

Wenn Sie sich lieber an meinem bestehenden Code orientieren möchten, finden Sie ihn hieraber ich empfehle Ihnen, diesem Beitrag zu folgen und es selbst zu bauen!

Die Struktur unseres Projektordners sieht wie folgt aus:

Initial project structure

Da dies ein kleines Projekt ist, wird der gesamte Python-Code in answerphone/__init__.pyenthalten, aber wenn es größer wäre, könnten Sie ihn in separate Module unter dem answerphone Paket aufteilen.

Außerdem legen Sie unsere statischen Ressourcen unter static und Ihre Vorlagen in templates und Flask weiß dann, wo sie zu finden sind.

Ich habe beschlossen, meine MP3-Aufnahmen in einem Ordner auf Projektebene zu speichern recordings Ordner, außerhalb des answerphone Pakets zu speichern, weil es eine gute Idee ist, Daten (insbesondere aus dem Internet heruntergeladene Dinge!) von ausführbarem Code zu trennen.

Sie können es auf dem Bild oben nicht sehen, aber es gibt auch eine .env Datei im Projektverzeichnis, die meine gesamte Konfiguration enthält.

Abhängigkeiten installieren

In meinem Projekt habe ich pip-tools verwendet, um meine Abhängigkeiten festzulegen, aber wenn Sie pip-tools noch nicht verwendet haben, empfehle ich Ihnen, das Folgende direkt in requirements.txt einzufügen und dann auszuführen pip install -r requirements.txt:

python-dotenv~=0.10 flask~=1.0 tinydb~=3.13 nexmo~=2.3

Ein kurzer Überblick über unsere Abhängigkeiten:

  • dotenv wird verwendet, um die Konfiguration aus unserer .env Konfigurationsdatei zu laden.

  • flask ist unser Web-Framework und Entwicklungs-Webserver.

  • tinydb ist eine wirklich einfache Datenbank, die alle Ihre Daten als json speichert.

  • nexmo ist die Nexmo Python Client Library und macht die Nutzung der Nexmo APIs einfacher als die manuelle Nutzung.

Öffnen Sie __init__.py in Ihrem answerphone Paket und geben Sie Folgendes ein:

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

Stellen Sie sicher, dass Sie Ngrok ausführen und starten Sie Ihren Entwicklungsserver mit:

FLASK_ENV=development FLASK_APP=answerphone flask run

Wenn Sie nun https://your-random-id.ngrok.io/answer mit Ihrem Webbrowser aufrufen, sollten Sie etwas wie das Folgende sehen:

[
    {
        "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
    }
]

Lassen Sie uns nun eine Voice-App erstellen und eine Nummer mit dieser URL verknüpfen. Führen Sie in Ihrer Konsole das Vonage CLI-Tool aus, das Sie Schritt für Schritt durch die Erstellung Ihrer Anwendung führt:

# Create an app vonage apps:create

Es wird etwas ausgedruckt wie Application created: 26aa5db4-546a-11e9-8f2d-0f348a273d3aund erstellt eine Datei namens private.key in Ihrem aktuellen Verzeichnis.

Nehmen Sie diese ID und fügen Sie sie in eine neue .env Datei wie folgt ein:

NEXMO_PRIVATE_KEY="./private.key" NEXMO_APPLICATION_ID=26aa5db4-546a-11e9-8f2d-0f348a273d3a

Lassen Sie dies vorerst so stehen - ich werde gleich erklären, wie man die Konfiguration lädt.

Wenn Sie eine Nummer kaufen müssen, empfehle ich Ihnen, dies über das Nexmo Dashboard.

Sobald Sie eine Numbers gekauft haben (stellen Sie sicher, dass sie Voice unterstützt!), kehren Sie zu Ihrer Befehlszeile zurück und verwenden den nexmo Befehl, um die Nummer mit Ihrer App zu verknüpfen:

# Replace the phone number with your own # and the application ID with your application ID! nexmo link:app 447700900606 26aa5db4-546a-11e9-8f2d-0f348a273d3a

Wenn Sie nun Ihre Nexmo-Nummer anrufen, sollten Sie die Nachricht in der talk Aktion oben: "Sie haben Oleg's Pizza erreicht. Bitte hinterlassen Sie eine Nachricht nach dem Signalton." Okay!

Überprüfen Sie Ihre Ngrok-Protokolle. Möglicherweise bemerken Sie einige 404-Fehler zu /event. Machen Sie sich darüber im Moment keine Gedanken - Sie werden später in diesem Tutorial einen Ereignis-Webhook hinzufügen.

Sobald Nexmo die Aufnahme Ihrer Nachricht beendet hat, stellt es leider eine POST-Anfrage an die URL in Ihrer record Aktion, die auf https://example.com/recording.

Lassen Sie uns das beheben, damit Sie das Aufnahmeereignis empfangen und die MP3-Datei herunterladen können, damit Ihr Handler Nachrichten von ihren Agenten abholen kann.

In Ihrem __init__.pyfügen Sie Folgendes hinzu:

# 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 ""

und ändern Sie nun Ihren answer Webhook. Die zweite Aktion sollte wie folgt aussehen:

{
    "action": "record",
    "beepStart": True,
    "eventUrl": [url_for("new_recording", _external=True)],
    "endOnSilence": 3,
},

Sie verwenden jetzt die Flask url_for Funktion, um eine URL zu erhalten, die auf den new_recording Webhook verweist, den Sie gerade zur Datei hinzugefügt haben.

Stellen Sie sicher, dass Ihr recording vorhanden ist, und starten Sie dann den Flask-Entwicklungsserver neu.

Wenn Sie nun Ihre Nexmo-Nummer anrufen und eine Nachricht hinterlassen, sollten Sie eine MP3-Datei in dem recording Ordner finden. Öffnen Sie sie in Ihrem Lieblings-MP3-Player, um zu hören, was sie sagt!

Wenn Sie möchten, können Sie jetzt aufhören - Sie haben alle Grundlagen darüber gelernt, wie Sie Nexmo dazu bringen, eine Nachricht aufzunehmen, und wie Sie diese Nachricht auf Ihren Server herunterladen können (Nexmo speichert die Aufnahme nur für ein paar Stunden für Sie).

Aber es wäre eine gute Idee, einige Metadaten zusammen mit dem Ton zu speichern, damit Sie wissen, wer der Anrufer war und wann er angerufen hat. Auf diese Weise können Sie eine Seite mit einer Liste aller Anrufe zu Ihrem Anrufbeantworter hinzufügen.

Ich wählte TinyDB um dies zu tun - es ist ein wirklich einfacher kleiner Datenspeicher, der Ihre Daten in eine JSON-Datei ausgibt. Es ist nicht sehr schnell, und es wird nicht speichern viele Daten sehr gut, aber es ist gut für dieses Projekt!

Fügen Sie Folgendes zu Ihrer .env Datei hinzu: DATABASE_PATH=answerphone.db.

Sie teilen TinyDB mit, dass die Daten in dieser Datei gespeichert werden sollen, indem Sie folgendes am Anfang Ihrer __init__.py Datei:

from tinydb import TinyDB, Query

db = TinyDB(os.environ["DATABASE_PATH"])

Fügen Sie nun Folgendes hinzu, um zwei "Tabellen" zum Speichern Ihrer Anruferdaten und Ihrer Aufzeichnungsdaten zu erstellen:

calls = db.table('calls')
recordings = db.table('recordings')

Sie müssen jetzt zwei Dinge tun: Sie müssen auf Anrufereignisse reagieren und die Anrufdaten aufzeichnen, wenn ein Anruf entgegengenommen wird; und Sie müssen ein paar Zeilen zu Ihrem recording Webhook einige Zeilen hinzufügen, damit die Aufzeichnungsdaten in der Datenbank gespeichert werden.

Fügen Sie zunächst den event Webhook hinzu:

@app.route("/event", methods=["POST"])
def event():
    if request.json.get('status') == 'answered':
        calls.insert(request.json)

    return ""

Die Zeile calls.insert(request.json) speichert alle JSON-Daten der Anfrage in der calls Tabelle, die Sie oben erstellt haben.

Fügen Sie nun eine ähnliche Zeile zu Ihrem recording Webhook, nach dem Code zum Speichern der MP3-Datei in Ihrem recordings Ordner zu speichern:

...

with open(f"recordings/{recording_id}.mp3", 'wb') as mp3_file:
    mp3_file.write(recording_bytes)

recordings.insert(request.json)

return ""

Rufen Sie erneut Ihre Nexmo-Nummer an und hinterlassen Sie eine Nachricht. Prüfen Sie, ob das Programm fehlerfrei läuft.

Wenn Sie einen Blick in answerphone.db schauen, sollten Sie eine Ladung gespeicherter JSON-Daten sehen. Laden wir diese Daten nun in eine schöne Webseite!

Fügen Sie zunächst eine Ansicht hinzu, mit der Sie eine MP3-Datei in den Browser laden können:

@app.route("/recordings/<uuid>")
def recording(uuid):
    response = make_response(open(f'recordings/{uuid}.mp3', 'rb').read())
    response.headers['Content-Type'] = 'audio/mpeg'
    return response

Der obige Code öffnet die binäre MP3-Datei, erstellt eine Antwort aus den Bytes und setzt dann den Content-Type-Header auf "audio/mpeg", den richtigen Typ für MP3-Daten.

Sie können dies testen, indem Sie die URL "/recordings/you-uuid-goes-here" mit der ID einer der MP3-Dateien in Ihrem Aufnahmeordner aufrufen.

Fügen Sie nun eine Ansicht hinzu, in der alle Aufzeichnungen sowie einige der mit jeder Aufzeichnung verbundenen Anrufdaten aufgelistet sind.

Dies kann durch eine kleine Helferklasse erleichtert werden.

Fügen Sie diesen Code am Anfang Ihrer __init__.py Datei ein:

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 = None

Diese Klasse ist so konzipiert, dass sie mit den JSON-Daten initialisiert wird, die dem recording Endpunkt bereitgestellten und in der recordings Tabelle in unserer Datenbank gespeichert werden.

Es sucht automatisch nach den zugehörigen Anrufdaten in der calls Tabelle und fügt sie dem Aufzeichnungsobjekt als related_call Attribut hinzu.

Schreiben Sie nun den folgenden View-Code, der für jede in der Datenbank gespeicherte Aufzeichnung eine Recording Instanz an die Ansicht für jede in der Datenbank gespeicherte Aufzeichnung übergibt:

@app.route("/")
def index():
    """
    A view which lists all stored recordings.
    """
    return render_template("index.html.j2", recordings=[Recording(r) for r in recordings])

Dies wird im Moment nicht funktionieren, da Sie keine Vorlagendatei erstellt haben!

Erstellen Sie eine Datei unter answerphone/templates/index.html.j2 und fügen Sie darin etwas wie das Folgende ein:

<!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>

Wenn Sie nun Ihre https://localhost:5000/ aufrufen, sollten Sie etwas wie das Folgende sehen:

Recording list

Du bist jetzt ein Meister der Spionage!

Ich werde zusammenfassen, was Sie gerade getan haben:

  • Sie haben auf einen eingehenden Telefonanruf mit einigen NCCO-Aktionen reagiert.

  • Sie haben Nexmo angewiesen, einen Teil eines Telefongesprächs aufzuzeichnen

  • Sie haben das Aufnahmeereignis behandelt, um die erstellte MP3-Datei herunterzuladen.

  • Sie haben Anrufdaten in einer Datenbank gespeichert und eine Wiedergabeliste erstellt, die im Internet abrufbar ist!

Weitere Informationen

Wenn Sie das soeben Gelernte noch ein wenig vertiefen möchten, können die folgenden Informationen hilfreich sein:

Sehen Sie sich auch das GitHub Repo für dieses Projekt, da ich den Code dokumentiert und die Listenansicht verbessert habe.

Nächste Schritte

Es gibt ein paar Möglichkeiten, dieses Projekt weiter zu entwickeln. Sie könnten einen Websocket verwenden, um den Browser zu benachrichtigen, wenn eine neue Aufzeichnung erscheint, sodass der Handler den Browser nicht neu laden muss, um Nachrichten von seinem Agenten zu erhalten.

Sie können auch die Nexmo's SMS API verwenden, um dem Handler eine SMS-Nachricht zu senden, wenn eine neue Aufnahme verfügbar ist!

Wenn Sie etwas Cooles machen, schicken Sie uns eine E-Mail an devrel@nexmo.com um es uns mitzuteilen!

Teilen Sie:

https://a.storyblok.com/f/270183/150x150/a3d03a85fd/placeholder.svg
Mark SmithVonage Ehemalige

Mark war nominell für die Client-Bibliotheken von Nexmo verantwortlich (obwohl er nur die Python- und Java-Bibliotheken schreibt). Er war ursprünglich Java-Entwickler, ist seit 18 Jahren Python-Entwickler und beschäftigt sich zunehmend mit Go und Rust. Er liebt es, Programmiersprachen bis an ihre Grenzen zu treiben und diese Techniken dann anderen Programmierern beizubringen. Er trägt einen Wikingerhut, ist aber kein Wikinger, und aus Gründen, die er nicht näher erläutern möchte, ist er Judy2k auf Twitter.