
シェア:
マークはNexmoのクライアント・ライブラリの名目上の責任者である(ただし、彼が書いているのはPythonとJavaのライブラリだけである)。もともとはJavaの開発者だったが、18年間Pythonの開発者であり、最近はGoやRustにも手を出している。プログラミング言語の限界に挑戦し、そのテクニックを他のプログラマーに教えるのが好きだ。バイキングの帽子をかぶっているがバイキングではない。
PythonとFlaskでボイスメールのデッドドロップを構築する方法
所要時間:6 分
私は公衆電話の薄汚れた受話器を取り、これまで何百回もしてきたように、その番号にダイヤルした。
"オレグ・ピザです。発信音の後にメッセージをどうぞ。
電話の向こうには生身の人間はおらず、ありもしないビジネスのロボット音声が流れているだけだった。
[BEEP]- どこかでテープの録音が始まった。私はメッセージを残した。
「こんにちは、チャックです。ペパロニとマッシュルームのピザをお願いします」。
私は受話器を置いて立ち去った。
私の名前はチャックではないし、ペパロニも好きではないが、これで私の正体はばれた。
私はスパイ・スリラーが大好きだが、スパイになる上で最も難しいことの一つは、自分のハンドラーにメッセージを残すためのデッドドロップを見つけることだ。幸いなことに、この投稿では、ウェブ上で誰かにメッセージを残すことができるデッドドロップ電話番号の作り方を紹介することで、皆さんのスパイ生活をより簡単にします。
前提条件
アーロンの素晴らしい投稿を読んだと思う。 Ngrokを使ったwebooksの開発方法.まだ読んでいないなら、今すぐ読んでほしい。
また、あなたがPythonとFlaskの基本的な知識を持っていると仮定する。
をインストールすることをお勧めします。 Vonage CLI ツールをインストールして をインストールすることをお勧めします。をインストールすることをお勧めします。 Nexmoダッシュボードで行うこともできます。
何を作るのか
Nexmo番号に電話をかけてメッセージを残すことができる、基本的なボイスメール・サービスの作り方を紹介しよう。
録音されたメッセージはあなたのサーバーにコピーされ、録音を一覧表示し、ブラウザで再生できるシンプルなウェブページを作成します。
プロジェクトの開始
もし、私の既存のコードに従いたいのであれば、以下のコードを参照してほしい。 こちらでも、この投稿を参考に自分で作ってみることをお勧めする!
プロジェクト・フォルダーの構造は次のようになっている:

これは小さなプロジェクトなので、Pythonコードはすべて answerphone/__init__.pyに入りますが、もっと大きなプロジェクトなら answerphoneパッケージに分割できます。
また、静的リソースを staticに、テンプレートを templatesに配置します。
私はMP3録音をプロジェクトレベルの recordingsフォルダーに保存することにしました。 answerphoneなぜなら、データ(特にインターネットからダウンロードしたもの!)を実行コードから分離するのは良い考えだからだ。
上の画像では見えないが、プロジェクト・ディレクトリーにも .envファイルがあります。
依存関係のインストール
私のプロジェクトでは pip-toolsを使って依存関係を固定した。 requirements.txtに貼り付けて実行することをお勧めします。 pip install -r requirements.txt:
依存関係を簡単に説明しよう:
dotenvは
.envを読み込むために使われる。フラスコは私たちのウェブフレームワークであり、開発用ウェブサーバーです。
tinydbは本当にシンプルなデータベースで、すべてのデータをjsonとして保存します。
ネクスモはNexmo Pythonクライアント・ライブラリで、Nexmo APIを手作業で使うよりも簡単に使えるようにします。
開く __init__.pyを開き answerphoneパッケージを開き、次のように入力する:
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,
},
]
)Ngrokを実行していることを確認し、開発サーバーを次のように起動する:
ウェブブラウザで https://your-random-id.ngrok.io/answerにアクセスすると、次のように表示される:
[
{
"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
}
]では、Voiceアプリを作成し、このURLに番号をリンクしてみましょう。コンソールでVonage CLIツールを実行すると、ステップバイステップでアプリケーションを作成できます:
のように表示されます。 Application created: 26aa5db4-546a-11e9-8f2d-0f348a273d3aというファイルが作成されます。 private.keyというファイルがカレント・ディレクトリに作成される。
このIDを新しい .envファイルに貼り付ける:
コンフィギュレーションをロードする方法は後で説明する。
番号を購入する必要がある場合は、次の方法で購入することをお勧めします。 Nexmoダッシュボード.
番号を購入したら(NumbersがVoiceに対応していることを確認しよう!)、コマンドラインに戻って、次のコマンドを使おう。 nexmoコマンドを使って番号をアプリにリンクさせる:
さて、ネクスモの番号に電話をかけると、上のアクションのメッセージが聞こえるはずです。 talkオレグのピザです。発信音の後にメッセージを残してください!
Ngrok のログを確認してください。404エラーが /event.このチュートリアルの後半でイベントウェブフックを追加します。
残念なことに、Nexmoはメッセージの録音を終えると、現在あなたの recordに設定されています。 https://example.com/recording.
録音イベントを受信してMP3をダウンロードし、ハンドラがエージェントからのメッセージを受信できるように修正しましょう。
あなたの __init__.pyに以下を追加する:
# 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 ""を修正し answerを修正します。2番目のアクションは以下のようになります:
{
"action": "record",
"beepStart": True,
"eventUrl": [url_for("new_recording", _external=True)],
"endOnSilence": 3,
},これでFlaskの url_for関数を使って new_recordingを指すURLを取得するためにFlaskの関数を使っています。
あなたの recordingフォルダが存在することを確認し、Flask 開発サーバを再起動します。
これで、Nexmoの番号に電話してメッセージを残すと、MP3ファイルが recordingフォルダにあるはずです。お好きなMP3プレーヤーで開いて、内容を聞いてみてください!
Nexmoにメッセージを録音させる方法、そしてそのメッセージをサーバーにダウンロードする方法(Nexmoは数時間だけ録音を保存します)についての基本はすべて学びました。
しかし音声と一緒にメタデータを保存しておけば、発信者が誰で、いつ電話をかけてきたかがわかる。そうすれば、アンサーフォンのデッドドロップにすべてのコールをリストしたページを追加することができる。
私が選んだのは TinyDBこれは本当にシンプルで小さなデータストアで、データをJSONファイルにダンプしてくれる。あまり高速ではないし、たくさんのデータをうまく保存することもできないが、このプロジェクトには問題ない!
あなたの .envファイルに追加する: DATABASE_PATH=answerphone.db.
このファイルにデータを保存するようにTinyDBに指示するには、ファイルの先頭付近に次のように記述します。 __init__.pyファイルの先頭で
from tinydb import TinyDB, Query
db = TinyDB(os.environ["DATABASE_PATH"])次に以下を追加し、発信者データと録音データを格納する2つの「テーブル」を作成する:
calls = db.table('calls')
recordings = db.table('recordings')今やるべきことは2つある:通話イベントに応答し、通話に応答したときに通話データを記録する必要があります。 recordingそして、録音データをデータベースに保存するために、Webhookに数行を追加する必要があります。
まず eventを追加します:
@app.route("/event", methods=["POST"])
def event():
if request.json.get('status') == 'answered':
calls.insert(request.json)
return ""この行は calls.insert(request.json)行は、リクエストのすべてのJSONデータを、上で作成した callsテーブルに格納します。
同じような行を recordingに同様の行を追加します。 recordingsフォルダに保存します:
...
with open(f"recordings/{recording_id}.mp3", 'wb') as mp3_file:
mp3_file.write(recording_bytes)
recordings.insert(request.json)
return ""もう一度Nexmo番号に電話をかけ、メッセージを残す。エラーなしで実行されることを確認してください。
内部を見ると answerphone.dbを見ると、保存されたJSONデータがロードされているのが見えるはずです。では、そのデータを素敵なウェブページにロードしてみましょう!
まず、MP3ファイルをブラウザに読み込むためのビューを追加する:
@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上のコードでは、バイナリのMP3ファイルを開き、バイトからレスポンスを作成し、content-typeヘッダーをMP3データの正しいタイプである'audio/mpeg'に設定している。
これをテストするには、録音フォルダ内のMP3ファイルのidを使って、"/recordings/you-uuid-goes-here "のURLをロードします。
次に、すべての録音と、各録音に関連する通話データの一部を一覧表示するビューを追加します。
少人数の助っ人クラスがいれば、もっと簡単にできる。
次のコードを __init__.pyファイルの先頭に
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このクラスは、エンドポイントに提供された JSON データを使用して初期化されるように設計されています。 recordingエンドポイントに提供され recordingsテーブルに格納されます。
テーブル内の関連するコールデータを自動的に検索し callsテーブルにある関連するコール・データを自動的に検索し、それをRecordingオブジェクトに related_call属性として録音オブジェクトに追加される。
次のビューコードを書いてください。 Recordingインスタンスを渡します:
@app.route("/")
def index():
"""
A view which lists all stored recordings.
"""
return render_template("index.html.j2", recordings=[Recording(r) for r in recordings])テンプレート・ファイルを作成していないため、現時点では失敗する!
にファイルを作成し answerphone/templates/index.html.j2にファイルを作成し、その中に以下のような内容を記述する:
<!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>これで https://localhost:5000/にアクセスすると、次のように表示されるはずだ:

これであなたもスパイマスターだ!
今やったことを要約しよう:
あなたはかかってきた電話にNCCOの行動で対応した。
Nexmoに電話の一部を録音するよう指示した。
作成したMP3ファイルをダウンロードするために、録音イベントを処理しました。
通話データをデータベースに保存し、ウェブブラウズ可能なプレイリストを作成した!
詳細情報
今学んだことをもう少し掘り下げたい場合は、以下が役に立つかもしれない:
また GitHub Repoをチェックしてみてください。
次のステップ
このプロジェクトをさらに進める方法がいくつかある。新しい録画が表示されたときにブラウザに通知するためにウェブソケットを使うことができます。そうすれば、ハンドラはエージェントからのメッセージを受け取るためにブラウザをリロードする必要がなくなります。
また NexmoのSMS APIを使うこともできます。を使用して、新しい録画が利用可能になったときにハンドラにSMSメッセージを送信することもできます!
何かクールなものを作ったら、私たちにメールを送ってください。 devrel@nexmo.comまでメールを送ってください!