https://a.storyblok.com/f/270183/96366/62fb05c064/pycascades-coc-hotline.png

Nexmo Voice APIによるPyCascades行動規範ホットラインの強化

最終更新日 May 4, 2021

所要時間:1 分

こんにちは、私の名前は マリアッタ.プラットフォームエンジニアとして Zapier.Pythonのコア開発者です。 PyCascadesカンファレンスの運営にも携わっています。

PyCascadesにて、 多様性は優先事項であり、後付けではありません。これを達成するための方法の一つは、強力な行動規範とその実施です。行動規範に関する問題の報告を容易にするために、主催者の一人である Alan Vezina は、行動規範(CoC)ホットラインを作りました。 行動規範(CoC)ホットライン.このホットラインは PyCon US 2018 や DjangoCon 2018 でも採用されています。

最初のPyCascades CoCホットラインの仕組みは以下の通りです。誰かが行動規範の問題を報告したい場合、ホットラインの番号に電話します。その時点で、PyCascadesの全オーガナイザーに通知され、通報者は最初に対応したオーガナイザーに接続されます。説明責任を果たすため、通話に関する情報はSlackチャンネルにも投稿され、通話記録が残ります。

ホットラインの開設以来、私は来年に向けてホットラインをどのように充実させることができるかを考えてきた。

このブログポストでは、Nexmo Voice APIとZapierを使ってPyCascades Code of Conduct Hotlineを拡張した方法を紹介します。

これらは強化されたホットラインの機能である:

  • 発信者に挨拶し、PyCascades行動規範ホットラインに到達したことを伝えます。発信者が正しい番号、つまり公式のPyCascades Code of Conduct Hotlineに到達したことを知ることが重要です。

  • すべての通話は自動的に録音されます。行動規範の報告は重要かつ微妙な問題です。録音があることで、私たちは説明責任を果たせるだけでなく、通話をさかのぼって再生することができるため、詳細を見逃すこともありません。

  • スタッフがおつなぎするまでの間、保留音楽が流れます。

  • オーガナイザーが電話に出ると、そのオーガナイザーを示すメッセージが流れます。マリアッタがこの通話に参加しています。

  • この通話がPyCascades行動規範に関するものであることを主催者に知らせるアラートを追加しました。私は電話を選別しています。1-800番号や知らない番号からの電話は無視することが多いです。会議期間中、これらの電話が行動規範の問題に関するものなのか(受けるべき)、それとも無料のクルーズを勧める電話勧誘なのか(無視する)を知る必要があります。

  • すべてのCoCコール活動の台帳は現在、グーグル・スプレッドシートに記録されている。

加えて、以下の機能はまだ機能する必要がある:

  • Slackに投稿されたCoCホットラインへの着信情報。Slack は PyCascades 主催者の主なコミュニケーション手段の一つです。Slackのメッセージは、通知と、たとえ誰も応答しなかったとしても通話があったという記録の両方の役割を果たします。

その他の技術情報

ホットラインはPythonで書かれており、私が選んだウェブ・フレームワークは aiohttpPython用の非同期ウェブ・サーバー&クライアント・フレームワークだ。aiohttpを使って、次のようなGitHubボットを作ったことがある。 ミスイスリントンブラックアウト.

ウェブサービスはHerokuにデプロイされている。PSFのウェブインフラのほとんどはHerokuでホスティングされているので、Pythonのコア開発者としては、他のタイプのクラウドインフラよりもHerokuに馴染みがある。

私にとってNexmo APIを選んだ主な理由の1つは、NexmoがPyCon US 2018、DjangoCon 2018、第1回PyCascades ?Nexmo APIを選んだもう一つの理由は、nexmo-pythonライブラリがオープンソースで利用可能で、新しいPythonバージョンと互換性があり、Python 3.7に対してテストされているからです。

のソースコードを見ることができます。 強化されたCoCホットライン.

Nexmoボイスアプリのセットアップ

まず、私のNexmo Voiceアプリケーションがどのようにセットアップされているかを説明しよう。

Nexmo Voice App settings screenshotNexmo voice app settings

NexmoでVoiceアプリケーションを設定する場合、イベントURLとアンサーURLの2つのWebhook URLを設定する必要があります。

イベントURLは常に必要です。通話状態に変化があった場合、Nexmoはここに情報を送信します。

イベントウェブフックの受信とアクティビティの記録

以下は、Event webhook によって配信されるペイロードの例です:

{
    "status": "started",
    "direction": "outbound",
    "from": "12025550124",
    "uuid": "80c80c80-80ce-80c8-80c8-80c80c80c80c",
    "conversation_uuid": "CON-be2be2be-a0dd-a0dd-a0dd-34b34b34b34b",
    "timestamp": "2018-10-25T17:42:17.552Z",
    "to": "12025550124"
}

発信者番号、どの番号にダイヤルされたか、通話のステータス、タイムスタンプ、一意の通話および会話識別子などの情報が含まれる。これはすべて、ログに記録できる有用な情報であり、各活動の記録が残る。

これらのWebhookを受信するために独自のWebサービスを作成する代わりに、私はZapier統合を作成しました。Zapierで使える統合機能のひとつが ZapierによるWebhooks.Webhooks by Zapierを使えば、コードを書いたりサーバを実行したりすることなく、あらゆるサービスからデータを受信したり、あらゆるURLにリクエストを送信したりすることができる。つまり、Webhooksを受け取ったり、渡したりすることができるのだ。

トリガーアクションとしてWebhooks by Zapierを使用して新しいZapを作成すると、ZapierはWebhooksの受信に使用できる "hooks.zapier.com "のURLを生成しました。このhooks.zapier.comのURLをNexmo Voice ApplicationのEvents URLとして提供しました。

Webhook Trigger ScreenshotWebhook Trigger

Nexmoからイベントウェブフックを受け取るようにZapierをセットアップしたので、いろいろなことができるようになった。まず、Slackとの統合を追加し、ホットラインへの着信についてCoCのプライベート・チャンネルに自動的にメッセージが投稿されるようにした。次に、Google Sheetsとの統合を追加し、ホットラインに関連するアクティビティが自動的にGoogle Sheetsの新しい行として追加されるようにした。

CoC Events screenshotCoC Events

電話応対

発信者がホットラインの番号をダイヤルすると、NexmoはそのイベントのペイロードをアンサーURLに送信します。アンサーURLはNCCO(Nexmoコールコントロールオブジェクト)を返す必要があります。

回答URLは私のウェブサービスの /webhook/answer/URLに設定されます。(ソースコード)

電話をかけてきた人に挨拶をして、PyCascades行動規範ホットラインにたどり着いたことを通知してほしかった。そのため、最初に返したNCCOは「話す」アクションです:

ncco = [
        {
            "action": "talk",
            "text": "You've reached the PyCascades Code of Conduct Hotline. This call is recorded."
        }
    ]

次に、発信者がホットラインにダイヤルしたときにイベントを受信するようになったので、スタッフ全員を呼び出して同じ通話に接続する必要があります。そのためには、発信者とスタッフを電話会議に追加する必要があります。

電話会議に発信者を追加するために、同じ名前の "conversation "NCCOアクションを追加する。

{
            "action": "conversation",
            "name": conversation name,
}

では、会話の "名前 "を作る必要があるのだろうか?必ずしもそうではない。アンサーURLの のペイロードを見てみよう。

answer_urlへのGETリクエストの例は以下の通りです:

/webhooks/answer?to=447700900000&from=447700900001&conversation_uuid=CON-aaaaaaaa-bbbb-cccc-dddd-0123456789ab&uuid=aaaaaaaa-bbbb-cccc-dddd-0123456789cd

ペイロードには conversation_uuid.会話に新しい名前を "作る "代わりに、同じ conversation_uuidを会話名として使うことにした。

そこで、リクエストから conversation_uuidを取得し、NCCOで使用した。

conversation_uuid = request.rel_url.query["conversation_uuid"].strip()
    ...
    {
            "action": "conversation",
            "name": conversation_uuid,
            ...
    }

会話を録音するには "record": Trueを指定します。録音が終了すると、NexmoはWebhookを eventUrlこのウェブフックのペイロードには、録音が保存されているURLが含まれます。

以下は、録画のペイロードの例です。 eventUrlウェブフック:

{
  "start_time": "2020-01-01T12:00:00Z",
  "recording_url": "https://api.nexmo.com/media/download?id=aaaaaaaa-bbbb-cccc-dddd-0123456789ab",
  "size": 12345,
  "recording_uuid": "aaaaaaaa-bbbb-cccc-dddd-0123456789ab",
  "end_time": "2020-01-01T12:01:00Z",
  "conversation_uuid": "bbbbbbbb-cccc-dddd-eeee-0123456789ab",
  "timestamp": "2020-01-01T14:00:00.000Z"
}

今回も、Webhookを受信するために独自のWebサービスを構成する代わりに、ZapierのWebhooksを利用した。録画のWebhookを受信するために別のZapを作成した。

Recording Zapier screenshotZapier Recording

このZapでは、Googleスプレッドシートとの統合を追加した。 recording_urlを含むペイロードの情報が自動的にGoogleスプレッドシートの新しい行として追加されるようにした。さらに、Slackとの統合を追加し、スタッフに新しい録画が通知されるようにした。

この時点で、NCCOの会話は次のようになる:

conversation_uuid = request.rel_url.query["conversation_uuid"].strip()
    ...
    {
            "action": "conversation",
            "name": conversation_uuid,
            "record": True,
            "eventUrl": [os.environ.get("ZAPIER_CATCH_HOOK_RECORDING_FINISHED_URL")],

            ...
    }

この時点で、ホットラインにやってもらいたいことが2つある。まず、各スタッフに電話をかけてもらい、会話に加えること。そしてもうひとつは、発信者が接続されるのを待っている間、音楽を流すことだ。

通話相手の待ち時間に音楽を流す

このコールで音楽を再生するのはいたって簡単だ。NCCOに "musicOnHoldURL"をNCCOに追加し、再生する音楽のURLを指定する:

"musicOnHoldUrl": ["https://..../music.mp3"]

音楽は ウィスティアのミュージック・コレクション特に『Let 'Em In Sessions』から。これらの音楽のライセンスは こちら.

import random
    
    MUSIC_WHILE_YOU_WAIT = [
        "https://assets.ctfassets.net/j7pfe8y48ry3/530pLnJVZmiUu8mkEgIMm2/dd33d28ab6af9a2d32681ae80004886e/oaklawn-dreams.mp3",
        "https://assets.ctfassets.net/j7pfe8y48ry3/2toXv1xuOsMm0Yku0YEGya/a792ce81a7866fc77f6768d416018012/broken-shovel.mp3",
        "https://assets.ctfassets.net/j7pfe8y48ry3/16VJzaewWsKWg4GsSUiwGi/9b715be5e8c850e46de98b64e6d31141/lennys-song.mp3",
        "https://assets.ctfassets.net/j7pfe8y48ry3/1qApZVYkxaiayA6aysGAOo/8983586c8ab4db8b69490718469a12f5/new-juno.mp3",
        "https://assets.ctfassets.net/j7pfe8y48ry3/6iXXKtJCp2oCMiGmsmAKqu/8163a8fe863405292ba3609193593add/davis-square-shuffle.mp3",
    ]
    
    ncco = {
        ...
        "musicOnHoldUrl": [random.choice(MUSIC_WHILE_YOU_WAIT)],
    }

他のスタッフを電話会議に呼ぶ

さて、各スタッフに電話をかけ、この通話に追加する必要がある。

これはNCCOではできないことだ。そこで Nexmo Pythonクライアントライブラリを利用した。これは pipでインストールできるので、requirements.txt ファイルに nexmoを追加した。

クライアントをインスタンス化するヘルパー関数を作った。

def get_nexmo_client():
        app_id = os.environ.get("NEXMO_APP_ID")
        private_key = os.environ.get("NEXMO_PRIVATE_KEY_VOICE_APP")
    
        client = nexmo.Client(application_id=app_id, private_key=private_key)
        return client

また、スタッフの電話番号を取得するためのヘルパー関数も作成した。電話番号はHerokuの環境変数として以下の形式で保存されている:

[
            {
                "name": "Mariatta",
                "phone": "12025550124"
            },
            {
                "name": "Miss Islington",
                "phone": "12025550123"
            }
        ]

ヘルパー関数は非常に簡単だ:

import json
    
    def get_phone_numbers():
        return json.loads(os.environ.get("PHONE_NUMBERS"))

これで、nexmoクライアントとダイヤルする電話番号を取得する機能ができたので、番号にダイヤルできるようになった。

Nexmo Pythonクライアントライブラリを使って電話番号を呼び出す:

response = client.create_call({
      'to': [{'type': 'phone', 'number': 12025550124}],
      'from': {'type': 'phone', 'number': 12025550123},
      'answer_url': ['https://example.com/answer']
    })

呼び出しメソッドで create_callcallメソッドでは to電話番号と from電話番号も入力する必要がある。電話番号は to電話番号は、私が電話をかけたいスタッフの電話番号です。

番号には from番号については、ホットラインの発信者の電話番号を伝える代わりに、その番号そのものを使った。 hotlineこうすることで、スタッフは発信者番号を見れば、この電話はホットラインからのものだとわかる。

はどうなんだ? answer_url?は? answer_urlは、スタッフがこのコールに応答したときのウェブフックです。ここでの望ましい動作は、電話に出たスタッフがホットラインの発信者がいる会話に追加されることです。そのため、ウェブフックにはNexmoが提供するペイロードに加えて conversation_name(これは conversation_uuid).

このウェブフックを処理するために、ウェブサービスに新しいエンドポイントを作成しました。 conversation_uuiduuidの両方をURLに含めることで、このWebhookを処理するための新しいエンドポイントを作成しました:

@routes.get(
        "/webhook/answer_conference_call/{origin_conversation_uuid}/{origin_call_uuid}/"
    )
    async def answer_conference_call(request):
    
        origin_conversation_uuid = request.match_info["origin_conversation_uuid"]
        origin_call_uuid = request.match_info["origin_call_uuid"]
        ...

このエンドポイントを作成すれば、ホットラインからの電話にスタッフが出るたびに、どの会話に追加すればいいのかがわかるようになる。

最後に、アンサーウェブフックは以下のようになる:

@routes.get("/webhook/answer/")
async def answer_call(request):
    conversation_uuid = request.rel_url.query["conversation_uuid"].strip()
    call_uuid = request.rel_url.query["uuid"].strip()

    ncco = [
        {
            "action": "talk",
            "text": "You've reached the PyCascades Code of Conduct Hotline. This call is recorded.",
        },
        {
            "action": "conversation",
            "name": conversation_uuid,
            "record": True,
            "eventMethod": "POST",
            "musicOnHoldUrl": [random.choice(MUSIC_WHILE_YOU_WAIT)],
            "eventUrl": [os.environ.get("ZAPIER_CATCH_HOOK_RECORDING_FINISHED_URL")],
            "endOnExit": False,
            "startOnEnter": False,
        },
    ]

    client = get_nexmo_client()
    phone_numbers = get_phone_numbers()

    for phone_number_dict in phone_numbers:
        client.create_call(
            {
                "to": [{"type": "phone", "number": phone_number_dict["phone"]}],
                "from": {
                    "type": "phone",
                    "number": os.environ.get("NEXMO_HOTLINE_NUMBER"),
                },
                "answer_url": [
                    f"https://mariatta-enhanced-coc.herokuapp.com/webhook/answer_conference_call/{conversation_uuid}/{call_uuid}/"
                ],
            }
        )
    return web.json_response(ncco)

電話会議にスタッフを追加する

この answer_conference_callエンドポイントは、スタッフを電話会議に追加する目的で作成された。これを達成するために、"conversation "アクションと会話の名前を含むNCCOをWebhookに返す必要がありました。しかし、スタッフが追加される前に、PyCascades Code of Conduct Hotlineに参加していることがわかるように挨拶したいと思います。

環境変数には PHONE_NUMBERS環境変数には電話番号の所有者の名前も含まれていることを思い出してほしい。

電話番号の所有者の名前を取得するために以下の関数を作成した:

def get_phone_number_owner(phone_number):
        phone_numbers = get_phone_numbers()
        for phone_number_info in phone_numbers:
            if phone_number_info["phone"] == phone_number:
                return phone_number_info["name"]
    
        return None

その機能があれば、私は次のようにスタッフに挨拶することができる:

@routes.get(
        "/webhook/answer_conference_call/{origin_conversation_uuid}/{origin_call_uuid}/"
    )
    async def answer_conference_call(request):
    
        to_phone_number = request.rel_url.query["to"]
        origin_conversation_uuid = request.match_info["origin_conversation_uuid"]
    
        phone_number_owner = get_phone_number_owner(to_phone_number)
    
        ncco = [
            {
                "action": "talk",
                "text": f"Hello {phone_number_owner}, connecting you to PyCascades hotline.",
            },
            {
                "action": "conversation",
                "name": origin_conversation_uuid,
                "startOnEnter": True,
                "endOnExit": True,
            },
        ]
        return web.json_response(ncco)

この時、皆さんは「? origin_call_uuidは何に使うのだろうと思うかもしれません。PyCascadesのどのスタッフが電話に出ているのか、ホットラインに電話をかけてきた人に知ってもらうための良い礼儀になると考えました。また、これは電話会議なので、複数の人が参加する可能性があることを覚えておいてください。誰かが無言で参加する代わりに、誰が参加したかを通話中の全員に知らせます。

client = get_nexmo_client()

    response = client.send_speech(
        origin_call_uuid, text=f"{phone_number_owner} is joining this call."
    )

これで answer_conference_callエンドポイントは次のようになる:

@routes.get(
        "/webhook/answer_conference_call/{origin_conversation_uuid}/{origin_call_uuid}/"
    )
    async def answer_conference_call(request):

        to_phone_number = request.rel_url.query["to"]
        origin_conversation_uuid = request.match_info["origin_conversation_uuid"]
        origin_call_uuid = request.match_info["origin_call_uuid"]
    
        phone_number_owner = get_phone_number_owner(to_phone_number)
        client = get_nexmo_client()
    
        try:
            response = client.send_speech(
                origin_call_uuid, text=f"{phone_number_owner} is joining this call."
            )
        except nexmo.Error as er:
            print(
                f"error sending speech to {origin_call_uuid}, owner is {phone_number_owner}"
            )
            print(er)
    
        else:
            print(f"Successfully notified caller. {response}")
    
        ncco = [
            {
                "action": "talk",
                "text": f"Hello {phone_number_owner}, connecting you to PyCascades hotline.",
            },
            {
                "action": "conversation",
                "name": origin_conversation_uuid,
                "startOnEnter": True,
                "endOnExit": True,
            },
        ]
        return web.json_response(ncco)

完了した通話の流れ

以上で、強化されたPyCascades行動規範が完成しました。

完全なコールフローは以下の通り:

  • 通報者がホットラインに電話をかける。

  • PyCascadesのスタッフは、ホットラインに着信があったという通知をSlackで受け取ります。

  • 通話に関する情報はGoogle Sheetsに追加される。

  • 発信者はメッセージを聞く:「PyCascades行動規範ホットラインへようこそ。この通話は録音されています。

  • 電話がつながるまでの間、音楽を聴くことができる。

  • PyCascadesの各スタッフはホットラインから電話を受ける。

  • PyCascadesのスタッフが電話に出ると、"Hello {staffname}, connecting you to PyCascades hotline." という声が聞こえます。

  • 一方、発信者は「{スタッフ名}がこの通話に参加しています」というメッセージを聞く。

  • スタッフと電話の相手は会話を続ける。

  • スタッフは電話を切り、その時点で通話録音は完了する。

  • スタッフは、新しい録画があることをSlackで通知される。

  • 録画情報もGoogle Sheetsに追加される。

録画のダウンロード

録画はNexmo Pythonクライアントを使用してダウンロードできます。 recording_urlは録画イベントのウェブフックで受け取った url です。

client = get_nexmo_client()
    recording = client.get_recording(recording_url)

通話録音はNexmoに1ヶ月間保存された後、自動的に削除されます。このような通話は重要で、録音を失いたくないので、次のようなコマンドラインスクリプトを作成しました。 録音をダウンロードする.

スクリプトは以下のように実行できる:

python3 -m download_recording url1 url2 url3 ...

スクリプトが実行されると、録画はダウンロードされ、ローカルにある recordingディレクトリに保存される。

結論

NexmoとZapierのおかげで、PyCascadesの行動規範ホットラインを強化できるようになった。このホットラインの設定は、以前よりも複雑になったようです。 以前.

しかし、自動記録やGoogleスプレッドシートでの自動ログといった新しい機能拡張は、スタッフ全員にとって有用だと信じているので、PyCascadesのためにこれをセットアップするために余分な時間を費やしても構わないと思っています。さらに、ハードコーディングする代わりにZapierを使うことで、統合機能を追加したい場合に柔軟に対応できます。

お読みいただきありがとうございます!ホットライン、PyCascades、Zapierに関してさらに質問がある場合は、遠慮なく mariatta.wijaya@zapier.com までメールしてください。


Nexmo Developer Relationsからのお知らせです:MariattaがNexmoを使ってPyCascadesのCode of Conductレポートをより良いものにする手助けをしてくれてとても嬉しいです。私たちはCoCを持つことが歓迎と包摂の空間を作るための重要な要素だと信じています。私たちは行動規範ホットラインを運営したいカンファレンスやミートアップをサポートします。イベント主催者の方で、Mariattaの行動規範ホットラインをご利用になりたい方は、下記までご連絡ください。 電子メール devrel@nexmo.comメールにてご連絡ください。アプリケーションのセットアップとNexmoの無料クレジットを喜んでサポートさせていただきます。

シェア:

https://a.storyblok.com/f/270183/150x150/a3d03a85fd/placeholder.svg
Mariatta

I am not open, parts of me are broken