https://a.storyblok.com/f/270183/69246/e281d17397/blog_family-hotline_1200x600.png

Crie uma linha direta para a família com a Vonage

Publicado em May 4, 2021

Tempo de leitura: 16 minutos

Qualquer empresa para a qual eu ligo sempre tem um desses sistemas de atendimento automatizado que me fazem perguntas antes de eu conseguir falar com uma pessoa de verdade, e como sou um pouco esquisito, sempre pensei: “por que não eu ter um desses?”

Minha filha começou o ensino médio há alguns meses, e o formulário que nos enviaram para preencher tinha apenas um único campo para um número de telefone de contato. O que acontece se dermos os meus dados e eu não estiver disponível? Para minha sorte, trabalho na Vonage e sei como configurar um número de telefone capaz de encaminhar chamadas para qualquer um de nós — ou para os dois! A Vonage vem ao resgate!

O que vamos construir

  1. A escola recebe um número da Vonage para servir como número de contato.

  2. Se ligarem para o número, uma mensagem automática oferece as seguintes opções:

    1. Liste os nomes dos pais, indicando o número que deve ser digitado para ser transferido para cada um deles.

    2. Se o chamador não fizer nada, encaminhe para o primeiro pai da lista.

    3. Pressione “*” em caso de emergência. Isso iniciará uma teleconferência e conectará os dois pais.

Eu tinha mais alguns requisitos:

  1. Seja simples e praticamente gratuito para hospedar e operar.

  2. Não é necessário gerenciar nenhum banco de dados, o que aumenta a simplicidade.

Como vamos construir isso

Escolhi duas tecnologias com as quais estou mais familiarizado: Python 3.6 e Flask. Escolhi o Python 3.6 porque essa foi a versão que veio com f-strings e elas são ótimas! Também estou usando o o pipenv para gerenciar minhas dependências do Python.

Essa combinação nos permite criar um excelente sistema IVR (Resposta Interativa de Voz) caseiro com pouco mais de 100 linhas de código!

Vamos lá!

Primeiro, vamos configurar nosso ambiente de desenvolvimento.

Como a Vonage precisa se conectar ao servidor que você vai criar, e seu laptop está protegido por um bom firewall, provavelmente você vai querer usar o ngrok para o desenvolvimento local — deixe-me recomendar a excelente post no blog sobre como usar o ngrok.

Provavelmente você vai querer instalar o pipenv globalmente e, a partir daí, vamos usá-lo para gerenciar o ambiente virtual do nosso projeto:

sudo pip install --upgrade pipenv

Agora vamos instalar as dependências necessárias para este projeto e ativar nosso ambiente virtual. Execute estes comandos no diretório do seu novo projeto — chamei o meu de hotline:

# Install compatible versions of `vonage`, `flask` and `attrs` pipenv install vonage~=2.5.5 flask~=1.0.2 attrs~=18.2.0 # Activate the virtualenv: pipenv shell

Agora vamos começar a escrever nosso servidor Flask.

Abra um arquivo chamado hotline.py, e digite o seguinte:

from flask import Flask, jsonify, url_for as url_for_, request


app = Flask(__name__)


@app.route("/incoming/", methods=["GET", "POST"])
def incoming():
    """
    An HTTP endpoint which handles incoming calls.

    :return: A JSON HTTP response containing the main menu NCCO actions.
    """
    return jsonify(
        [
            {
                "action": "talk",
                "text": "Welcome to the Brockman family hotline",
                "voiceName": "Amy",
            }
        ]
    )

Agora, em janelas separadas do terminal , você deve executar o seguinte ao mesmo tempo:

# Tunnel requests to local port 5000 (Flask's default port): ngrok http 5000
# Start your Flask server: FLASK_APP=hotline flask run --debugger --reload

Ao executar o ngrok, ele exibe o nome de domínio gerado que será redirecionado para o seu servidor. Ficará mais ou menos assim: https://abcde1234.ngrok.io -> localhost:5000. Agora você pode testar seu aplicativo Flask em https://abcde1234.ngrok.io/incoming (substitua abcde1234 pelo que foi exibido no seu terminal).

Você deve ver algo assim:

First NCCO testFirst Test

Ligue para o seu servidor

Agora você precisa configurar um Número Virtual da Vonage para ligar para o seu servidor quando alguém ligar para o seu número. Portanto, agora você precisa de três coisas:

  1. A Account da Vonage

  2. A Voice application configurada

  3. Um número virtual da Vonage configurado.

Criar um Account na Vonage

Instale o CLI da Vonage

Instale a CLI do Vonage globalmente com este comando:

npm install @vonage/cli -g

Em seguida, configure a CLI com sua chave e seu segredo da API da Vonage. Você pode encontrar essas informações no Painel do Desenvolvedor.

vonage config:set --apiKey=VONAGE_API_KEY --apiSecret=VONAGE_API_SECRET

Criar um aplicativo de voz

Crie um novo diretório para o seu projeto e acesse-o com o comando `cd`:

mkdir my_project
CD my_project

Agora, use a CLI para criar um aplicativo da Vonage.

vonage apps:create ✔ Application Name … hotline ✔ Select App Capabilities › Voice ✔ Create voice webhooks? … yes ✔ Answer Webhook - URL … https://ed330676.ngrok.io/incoming/ ✔ Answer Webhook - Method › POST ✔ Event Webhook - URL … https://ed330676.ngrok.io/event/ ✔ Event Webhook - Method › POST ✔ Allow use of data for AI training? Read data collection disclosure … yes Application created: 34abcd12-ef12-40e3-9c6c-4274b3633761

É bom você anotar esse ID que aparece impresso depois Application created:. Você vai precisar dele na próxima etapa.

Compre um número e vincule-o ao seu aplicativo de voz

Agora você precisa de um número para poder receber chamadas. Você pode alugar um usando o comando a seguir (substituindo o código do país pelo seu). Por exemplo, se você estiver nos EUA, substitua GB por US:

vonage numbers:search US vonage numbers:buy [NUMBER] [COUNTRYCODE]

Agora, vincule o número ao seu aplicativo:

vonage apps:link --number=VONAGE_NUMBER APP_ID

Tudo bem!

Agora você pode testar tudo isso ligando para o número de telefone que acabou de comprar. Você deve ouvir a Amy ler a mensagem “Bem-vindo à linha direta da família Brockman”. Se ouvir uma mensagem informando que o número não foi reconhecido, ou se algo não funcionar, abra a página de depuração do ngrok em http://127.0.0.1:4040/ no seu navegador para ver quais solicitações chegaram e se o servidor respondeu corretamente.

Resposta à INPUT

Vamos alterar a mensagem e adicionar uma opção de seleção ao nosso menu NCCO para que o usuário possa escolher com quem deseja falar:

@app.route("/incoming/", methods=["POST", "GET"])
def incoming():
    """
    An HTTP endpoint which handles incoming calls.
    """
    return jsonify(
        [
            {
                "action": "talk",
                "text": """
                    Welcome to the Brockman family hotline.
                    To speak to Pete Brockman, please press 1.
                    To speak to Sue Brockman, please press 2.
                """,
                "voiceName": "Amy",
                "bargeIn": True,
            },
            {
                "action": "input",
                "eventUrl": [url_for("family_selection")],
                "maxDigits": 1,
            },
        ]
    )


@app.route("/family-selection/", methods=["POST"])
def family_selection():
    """
    An HTTP endpoint which handles the DTMF input from the main menu.
    """
    postdata = request.json

    if postdata["timed_out"]:
        index = 0
    else:
        index = int(postdata["dtmf"]) - 1
    return jsonify([{
        "action": "talk",
        "text": f"You selected the {index} option",
    }])

Não se precipite ainda! Ainda não estamos totalmente prontos... mas, deixe-me explicar como isso funciona:

Adicionei isso à talk ação:

"bargeIn": True,

Isso significa que a pessoa que está ligando pode digitar um número no celular enquanto a talk ação ainda estiver lendo a mensagem para ele. Isso ativará automaticamente a ação seguinte input . Também adicionei a seguinte ação:

{
    "action": "input",
    "eventUrl": [url_for("family_selection")],
    "maxDigits": 1,
},

Essa input ação instrui o Vonage a chamar a family_selection função que também acabamos de adicionar. A função url_for fornece a URL correspondente ao nome da função especificado.

Um uso interessante de partial

url_for por padrão, gera um URL relativa . URLs relativos não são compatíveis com o Vonage Voice — por isso, adicionei o seguinte no início do arquivo:

from functools import partial

from flask import url_for as url_for_

# We don't have any use for relative URLs, so hard-code this param:
url_for = partial(url_for_, _external=True)

Esse uso de conjuntos parciais transforma a url_for função para algo mais útil — é uma cópia da função do Flask url_for , mas com o valor padrão do _external parâmetro definido como True. (Esqueci de definir esse parâmetro tantas vezes antes de resolver isso com essa solução!)

Depois de adicionar isso ao seu arquivo e verificar se o servidor foi reiniciado, é hora de ligar para o seu número novamente. O sistema deve ler a mensagem para você, permitir que você digite um número no seu telefone e, em seguida, ler esse número subtraindo um dele. Se você esperar alguns segundos, ele deve informar que você selecionou zero.

Por que estou subtraindo 1 do número que eles digitaram? É para que possamos selecionar uma pessoa de uma lista! Se o teste acima deu certo, vamos colocar nossas pessoas em uma lista e tornar esse menu um pouco mais dinâmico.

Reencaminhamento de chamadas

A primeira coisa de que realmente precisamos é uma maneira melhor de configurar nossos “pais”. É bom ter uma lista das pessoas para quem queremos encaminhar as chamadas. Por enquanto, vamos definir os detalhes deles no arquivo Python. Por favor, não façam isso com números de telefone reais e não enviem essas alterações para um repositório público!

import attr

@attr.s
class Endpoint:
    """ A data class containing a potential callee's details. """
    name = attr.ib()
    phone_number = attr.ib()

ENDPOINTS = [
    Endpoint(name="Pete Brockman", phone_number="447700900123"),
    Endpoint(name="Sue Brockman", phone_number="447700900456"),
]
VONAGE_NUMBER = "447700900847"

No código acima, estou usando o incrível biblioteca attrs para definir uma classe simples, Endpointpara armazenar o nome e o número de telefone de uma pessoa. Em seguida, estou criando uma lista de Endpointpessoas para as quais possamos querer repassar chamadas.

Agora vou adicionar uma função utilitária simples para gerar talk ações. Isso tornará o código mais legível e significa que, por padrão, usaremos a Amy Voice. Você pode obter uma lista de todas as vozes compatíveis com o Vonage aqui

def talk(message, voice="Amy", barge_in=None):
    """ Utility function to generate a `talk` NCCO action. """
    response = {"action": "talk", "text": message, "voiceName": voice}
    if barge_in is not None:
        response["bargeIn"] = barge_in

    return response

E agora separou o código para gerar a mensagem do menu principal:

def make_answer_message(message, endpoints):
    """
    Generate a script to be read to the caller, informing them of their options.
    """
    endpoint_options = [
        f"To speak to {endpoint.name}, please press {code}."
        for code, endpoint in enumerate(endpoints, start=1)
    ]

    return " ".join([message, *endpoint_options, "If you're not sure, please hold."])

Você percebeu que *endpoint_options na última linha do código acima? Você sabia que é possível fazer isso no Python 3 para expandir uma lista em outra lista? Nesse caso, o resultado é que acabamos com uma lista de strings que podemos unir com espaços.

Agora você pode substituir a talk ação no incoming método pela seguinte chamada à talk função:

talk(
    make_answer_message(
        "Welcome to the Brockman family hotline.", ENDPOINTS
    ),
    barge_in=True,
),

E, por fim, você pode modificar a family_selection função para que ela realmente encaminhe a chamada para um número de telefone:

@app.route("/family-selection/", methods=["POST"])
def family_selection():
    """
    An HTTP endpoint which handles the DTMF input from the main menu.
    """
    postdata = request.json

    try:
        if postdata["timed_out"]:
            index = 0
        else:
            index = int(postdata["dtmf"]) - 1

        if index < len(ENDPOINTS):
            # They elected to speak to an individual
            endpoint = ENDPOINTS[index]
            return jsonify(
                [
                    talk(f"Connecting to {endpoint.name}"),
                    {
                        "action": "connect",
                        "from": VONAGE_NUMBER,
                        "endpoint": [{"type": "phone", "number": endpoint.phone_number}],
                    },
                ]
            )
    except ValueError:
        pass  # This is raised by `int`, and can be ignored - we just forward on to the following error message...

    return jsonify([talk("I didn't understand that option.")])

O código acima parece bem mais complicado do que antes, mas isso é porque agora estamos fazendo algumas verificações de erros e informando ao usuário se ele pressionou uma tecla que não esperávamos. Se o usuário pressionar 1 ou 2, ouvirá uma mensagem informando que está sendo conectado e, em seguida, será conectado ao número do ponto de extremidade.

Bom, então, o que já fizemos até agora?

  • A escola pode ligar para o meu número e lá vão ler uma lista de pessoas com quem podem entrar em contato.

  • Se digitarem o 1 ou o 2, serão conectados a essa pessoa. (A propósito, nenhuma das partes da ligação consegue ver o número de telefone da outra, então essa é uma ótima maneira de tornar as ligações anônimas!)

  • Se a pessoa que está ligando esperar, ela será conectada ao primeiro terminal da lista configurada.

Poderíamos parar por aqui, se quiséssemos, mas eu tinha um terceiro requisito: em caso de emergência, quem estivesse ligando poderia apertar o star, e o sistema ligaria para nós dois e nos colocaria em uma teleconferência.

Adicionando uma opção de teleconferência

Uma teleconferência é criada com uma conversation ação e termina (por padrão) quando não há mais ninguém na chamada. A única informação necessária para conectar uma pessoa a uma teleconferência é o nome que atribuímos à teleconferência na primeira conversation ação.

Inicializar um objeto Cliente

Vamos precisar de um objeto Vonage Client para fazer chamadas para os pais; por isso, ainda bem que instalamos a biblioteca Python do Vonage no início deste tutorial, certo? Coloque as seguintes linhas no início do seu arquivo. Se você quiser , você pode colar seu application_id e private_key valores diretamente no arquivo, mas acho melhor carregá-los a partir de variáveis de ambiente. É muito fácil enviá-los para um repositório público, e qualquer pessoa que os tenha pode seu saldo da Vonage!

import vonage

vonage_client = vonage.Client(
    application_id=os.getenv('VONAGE_APPLICATION_ID'),
    private_key=os.getenv('VONAGE_PRIVATE_KEY')
)

Informe ao usuário que ele pode pressionar a tecla “*”

Antes de fazermos isso, adicione a mensagem "If this is an emergency, please press star." à string retornada pela nossa make_answer_message função. Eu a adicionei logo após o message item da lista.

Criar uma teleconferência

Agora vamos criar nossa teleconferência. Respire fundo; é um trecho de código bem grande.

Precisaremos de um novo endpoint HTTP, chamado conference_ncco, que fornecerá ações NCCO para cada um dos participantes que estiverem sendo conectados à teleconferência.

Extraí todo o código responsável por fazer chamadas para os pais e gerar ações NCCO para o chamador, colocando-o em uma função chamada create_conference_call.

Vou juntar as duas funções para que você possa ver como o parâmetro conference_id, que é gerado em create_conference_call , é incorporado no caminho da URL para o conference_ncco ponto de extremidade, para que ele saiba o nome da teleconferência a ser incorporado na conversation ação NCCO.

@app.route("/conference/<conference_id>/ncco", methods=["GET", "POST"])
def conference_ncco(conference_id: str):
    """
    An HTTP endpoint which generates the NCCO actions to connect a callee to
    a conference call.
    """
    return jsonify(
        [
            talk("You are being connected to a family hotline conference call."),
            {"action": "conversation", "name": conference_id},
        ]
    )

def create_conference_call(endpoints):
    """
    Generate an NCCO response to connect the caller to all the provided
    `endpoints` in a single conference call.
    """
    # Generate a unique name for our conference:
    conference_name = str(uuid.uuid4())

    # Loop through the endpoints and dial them into the conference call:
    for endpoint in endpoints:
        vonage_client.create_call(
            {
                "to": [{"type": "phone", "number": endpoint.phone_number}],
                "from": {"type": "phone", "number": VONAGE_NUMBER},
                "answer_url": [
                    url_for("conference_ncco", conference_id=conference_name)
                ],
            }
        )
        print(f"Dialing {endpoint.phone_number} into {conference_name}")

    # Connect the inbound leg to the conference call we're creating:
    return jsonify(
        [
            talk("Connecting all parties."),
            {
                "action": "conversation",
                "name": conference_name,
            },
        ]
    )

Processar uma entrada com o símbolo de estrela

Agora, só precisamos modificar family_selection para que ele saiba o que fazer com um * código DTMF, colocando isso no início da função:

if postdata["dtmf"] == "*":
    return create_conference_call(ENDPOINTS)

Experimente!

Ligue para o seu número da Vonage e siga todas as instruções do menu principal para verificar se tudo está funcionando! Se estiver tudo funcionando, então é hora de colocar em operação!

Implemente!

Se você acessar o repositório Git deste projeto, você verá que fiz algumas pequenas alterações, incluindo o carregamento de toda a configuração a partir de variáveis de ambiente e a adição de um Procfile. Isso deve significar que é relativamente simples fazer a implantação no Heroku — mas vou deixar isso como um exercício para você! Não se esqueça de atualizar a configuração do seu aplicativo Vonage para apontar para a nova URL do Heroku, em vez da URL do ngrok que você vem usando para desenvolvimento.

Depois que estiver instalado, é hora de entrar em contato com a escola do seu filho e pedir que atualizem seus dados de contato!

O que fizemos?

Criamos um sistema completo de IVR para que a escola possa entrar em contato conosco! Ele pode encaminhar chamadas para números específicos ou conectar todos em uma única teleconferência!

Créditos adicionais

Outras coisas que pensei em fazer com este servidor incluem:

  • Encaminhar as mensagens SMS enviadas para o número aos dois pais.

  • Envio de mensagens de texto para informar aos pais caso tenham perdido uma ligação da escola.

  • Criar uma chamada do tipo “race” que ligue para ambos os pais, e o primeiro a atender receba a chamada. A segunda chamada seria interrompida e o destinatário receberia um SMS informando que o outro pai havia resolvido a situação.

  • Se nenhum dos pais atender, a escola poderá gravar uma mensagem, que ficará disponível para que ambos os pais possam ouvir.

  • Integração com um canal do Slack da família, com alertas para chamadas recebidas, encaminhamento de mensagens SMS para o Slack e até mesmo a publicação de mensagens gravadas para serem reproduzidas no Slack!

Como você pode ver, há muito potencial assim que você começa. Espero que você tenha se divertido acompanhando este tutorial. Sou a @judy2k no Twitter — me sigam ou me façam perguntas sobre este tutorial!

Compartilhar:

https://a.storyblok.com/f/270183/150x150/a3d03a85fd/placeholder.svg
Mark SmithEx-funcionários da Vonage

Mark era o responsável nominal pelas bibliotecas de clientes da Nexmo (embora ele só desenvolva as bibliotecas em Python e Java). Ele começou como desenvolvedor Java, já trabalha com Python há 18 anos e vem se aventurando cada vez mais com Go e Rust. Ele gosta de levar as linguagens de programação ao limite e, depois, ensinar essas técnicas a outros programadores. Ele tem um chapéu de viking, mas não é um viking, e no Twitter usa o nome Judy2k por motivos que prefere não revelar.