
Compartilhar:
Aaron era um defensor de desenvolvedores na Nexmo. Engenheiro de software experiente e aspirante a artista digital, Aaron costuma ser visto criando coisas com código ou com eletrônica; às vezes, os dois. Geralmente, dá para perceber quando ele está trabalhando em algo novo pelo cheiro de componentes queimando no ar.
Reproduza “Last Christmas” durante uma ligação telefônica usando Python
Tempo de leitura: 6 minutos
O Natal é uma época cara, mas posso ajudar você a economizar uma fortuna em presentes, garantindo que ninguém fale com você no dia de Natal — basta mandar todo mundo para Whamhalla!
Todo ano, meus amigos e eu participamos do Whamageddon, e as regras são simples:
O objetivo é aguentar o máximo de tempo possível sem ouvir o clássico natalino do WHAM, “Last Christmas”
O jogo começa no dia 1º de dezembro e termina à meia-noite do dia 24 de dezembro
Apenas a versão original é válida
Você está fora assim que reconhecer a música
Qualquer pessoa que já tenha saído de casa ou ligado o rádio durante o Natal pode confirmar como é difícil evitar ouvir “Last Christmas”. A partir do primeiro segundo após a meia-noite do dia 1º de dezembro, é como se todas as lojas estivessem tocando essa música sem parar. A maioria das pessoas adota uma estratégia defensiva, evitando os lugares onde é mais provável que a música esteja tocando. Mas nós não somos como a maioria; vamos partir para o ataque.
Os Princípios da Guerra
Santa driving a tank
Conquistar, manter e aproveitar a iniciativa. A ação ofensiva é a maneira mais eficaz e decisiva de alcançar um objetivo comum claramente definido. As operações ofensivas são o meio pelo qual uma força militar conquista e mantém a iniciativa, ao mesmo tempo em que preserva a liberdade de ação e alcança resultados decisivos. Isso é fundamentalmente verdadeiro em todos os níveis da guerra.
Podemos vencer o Whamageddon seja evitando a música até o dia 25 de dezembro, seja garantindo que todos os nossos amigos a ouçam antes de nós, para que sejamos os últimos a resistir. Precisamos encontrar uma maneira de fazer com que eles ouçam a música, sem que a gente tenha que ouvi-la ao mesmo tempo. Nada de “fogo amigo”, por favor!
O vetor de ataque mais óbvio seria enviar a eles um link do YouTube com a música, mas o “Rickrolling” deixou todo mundo com muito receio de clicar em links desconhecidos do YouTube. Precisamos de um canal que eles não suspeitem.
Atacar o inimigo em um momento, local ou de uma maneira para os quais ele não esteja preparado. O elemento surpresa pode alterar de forma decisiva o equilíbrio do poder de combate. Ao buscar o elemento surpresa, as forças podem alcançar um sucesso muito superior ao esforço despendido. O elemento surpresa pode estar no ritmo da ação, no tamanho da força, na direção ou localização do esforço principal e no momento certo. O engano pode aumentar a probabilidade de se alcançar o elemento surpresa.
Transmissão de áudio durante uma ligação telefônica
Tanto quanto “Last Christmas” esteja em toda parte nesta época do ano, acho que nunca atendi o telefone e ouvi essa música. Ninguém jamais suspeitaria que o Wham! estivesse ligando.
Vamos usar duas APIs para fazer isso, a Voice API da Nexmo para fazer a chamada e reproduzir o arquivo MP3 para nossos amigos, e a API do Spotify para fornecer um pequeno trecho de “Last Christmas”. Ambas as APIs têm wrappers em Python para facilitar o trabalho, e vamos integrar tudo isso em uma interface de linha de comando (CLI) bacana para que possamos “eliminar” nossos amigos com um único comando. Quando terminarmos, ficará exatamente assim:
Screencast of our Voice Application CL
Introdução
Você vai precisar de um pouco de experiência com Python para fazer isso funcionar, e pelo menos a versão 3.6, já que estamos usando f-strings (porque elas são incríveis) , além de algumas outras coisas:
Um Account no Spotify e seu próprio aplicativo do Spotify. Anote seu ID de cliente e sua chave secreta, pois você precisará adicioná-los ao seu
.envmais tardeNgrok; se você não sabe ao certo para que serve, é melhor ler primeiro nosso tutorial sobre o ngrok
Configuração
Há um arquivo de exemplo .env.example no repositório; renomeie-o para .env e comece a preencher os valores. Para o NEXMO_APPLICATION_ID e NEXMO_PRIVATE_KEY , você precisa criar um novo aplicativo Nexmo; você pode fazer isso pelo Painel de Controle. Lembre-se de salvar o gerado private.key em um local seguro e definir o valor de NEXMO_PRIVATE_KEY para o local onde sua chave está armazenada.
O script também possui várias dependências; recomendo instalá-las via pipenv, mas você pode usar algum outro gerenciador de dependências do Python, se preferir.
pipenv install
Depois de instalar as dependências, certifique-se de ter ativado seu ambiente virtual (isso seria pipenv shell se você estiver usando o pipenv) e que seu shell contenha as variáveis de ambiente do .env arquivo. Por fim, precisamos expor nosso script à internet pública para que os webhooks do Nexmo possam acessá-lo. Para isso, usaremos o ngrok; não se preocupe com a configuração do túnel, apenas certifique-se de que o ngrok esteja em execução.
ngrok http 80
Agora você está pronto para executar o script.
python pvpwham.py NUMBER
Existem algumas opções adicionais que você pode especificar; execute python pvpwham.py --help para obter mais informações.
Vamos examinar algumas das partes mais interessantes do código com mais detalhes.
Validação do número
É fundamental garantir que tenhamos um número de telefone válido para contatar; sem ele, nada mais funciona. Por isso, dedicamos algum tempo para validar o número e convertê-lo para o formato correto. No entanto, as pessoas escrevem números de telefone das mais variadas e inusitadas maneiras; portanto, se não conseguirmos validá-lo, solicitamos que sigam o padrão E.164 e até fornecemos um link com mais informações antes de pedir que tentem novamente.
while e164_number == False:
insight_response = nexmo_client.get_basic_number_insight(number=number)
if insight_response["status"] == 3:
insight_response = nexmo_client.get_basic_number_insight(
number=number, country=country
)
if insight_response["status"] != 0:
click.clear()
click.secho(intro, bg="magenta", fg="green")
click.secho(
f"{number} does not appear to be a valid telephone number",
bg="magenta",
fg="white",
)
click.secho(
"It might work if you enter it in the E.164 format",
bg="magenta",
fg="white",
)
if click.confirm(wtf_e164_message):
click.launch(
"https://developer.nexmo.com/concepts/guides/glossary#e-164-format"
)
if click.confirm(try_number_again_message):
number = click.prompt("Ok, give it to me in E.164 this time")
else:
raise click.BadArgumentUsage(
click.style(
f"{number} does not appear to be a valid number. Try entering it in the E.164 format",
bg="red",
fg="white",
bold=True,
)
)
else:
e164_number = insight_response["international_format_number"] Encontrar um arquivo MP3 da música
Não precisamos da música inteira; alguns segundos já devem bastar para levar alguém ao Whamhalla. A prévia de 30 segundos do Spotify é ideal. O script usa “Last Christmas” como padrão, mas você pode configurá-lo usando a --track opção.
spotify_client_credentials_manager = SpotifyClientCredentials(
client_id=os.environ["SPOTIFY_CLIENT_ID"],
client_secret=os.environ["SPOTIFY_CLIENT_SECRET"],
)
spotify_client = spotipy.Spotify(
client_credentials_manager=spotify_client_credentials_manager
)
tracks = spotify_client.search(track, limit=1, type="track")
if len(tracks["tracks"]["items"]) == 0:
raise click.BadOptionUsage(
track,
click.style(f"Can't find track: {track}", bg="red", fg="white", bold=True),
)
track = tracks["tracks"]["items"][0] Nosso servidor
Temos vários webhooks diferentes da Nexmo que precisamos gerenciar.
URL de resposta — ela é chamada sempre que alguém atende a chamada e contém uma lista de ações que a Nexmo deve executar. No nosso caso, vamos instruir a Nexmo a gravar a chamada e a usar a função de conversão de texto em fala para ler um breve aviso ao usuário (essa função está desativada por padrão, mas pode ser ativada usando a --delay opção), e, por fim, transmitir nossa prévia do Spotify durante a chamada
@cherrypy.tools.json_out()
def index(self, **params):
ncco_file = [
{
"action": "record",
"eventUrl": [f"{self.ngrok_tunnel['public_url']}/recording"],
}
]
if delay == "short":
ncco_file.append({"action": "talk", "text": "whamageddon"})
elif delay == "long":
ncco_file.append(
{
"action": "talk",
"text": "hang up your phone or prepare to enter Whamhalla",
}
)
ncco_file.append(
{"action": "stream", "streamUrl": [f"{self.preview_url}?t=mp3"]}
)
return ncco_fileURL da gravação — Assim que o usuário desligar o telefone e a chamada for encerrada, a Nexmo aciona esse webhook com os detalhes da gravação para que possamos baixar o arquivo MP3 e ouvir o desânimo do nosso amigo ao perceber o que aconteceu.
Observação: Gravamos tudo, inclusive o áudio que transmitimos durante a chamada. Você não vai querer acabar indo para Whamhalla sem querer também. Você pode modificar o script para usar gravações separadas para evitar isso.
@cherrypy.expose
def fetch_recording():
data = cherrypy.request.json
click.secho("## Fetching Call Recording", bg="green", fg="black", bold=True)
recording_response = nexmo_client.get_recording(data["recording_url"])
recordingfile = f"/tmp/{data['recording_uuid']}.mp3"
os.makedirs(os.path.dirname(recordingfile), exist_ok=True)
with open(recordingfile, "wb") as f:
f.write(recording_response)
click.secho("## Call Recording Saved", bg="green", fg="black", bold=True)
if click.confirm(
click.style(
"## Listen to your friend's anguish now?", bg="magenta", fg="white"
)
):
click.launch(recordingfile)URL de eventos — Isso serve apenas para fins informativos. O script atualiza o terminal com o status mais recente que recebe por meio do webhook de eventos.
@cherrypy.expose
@cherrypy.tools.json_in()
def events(self):
data = cherrypy.request.json
click.secho(
f"## Status: {data['status']}", bg="blue", fg="white", bold=True
)
return "OK" Serviço de limpeza
Sempre que a chamada for concluída, temos um on_end_request função que faz algumas tarefas de limpeza. Desligamos nosso servidor Cherrypy e encerramos nosso túnel Ngrok.
def quit_cherry():
cherrypy.engine.exit()
click.secho("## Exiting NCCO Server", bg="blue", fg="white", bold=True)
requests.delete("http://localhost:4040/api/tunnels/pvpwham")
click.secho("## Closing tunnel", bg="blue", fg="white", bold=True) Outros comandos divertidos
Dá pra encontrar algumas coisas bem estranhas no Spotify, se você tiver criatividade suficiente.
python pvpwham.py NUMBER --track='Rick Astley Never gonna give you up'
python pvpwham.py NUMBER --track='Sound Effects Animals Chimps, Apes'
python pvpwham.py NUMBER --track='Halloween Sound effects machine ghostly whispers'
E agora?
Orientar todas as operações militares para um objetivo claramente definido, decisivo e alcançável. O objetivo militar fundamental da guerra é a destruição da capacidade de combate e da vontade de lutar do inimigo.
Mesmo que você não consiga eliminar todos os seus amigos, espero que os sobreviventes fiquem tão ansiosos toda vez que o telefone tocar que acabem se rendendo. Entrando no shopping local para sentar e esperar pelo inevitável.
Então, depois de ter derrotado a oposição, dê uma olhada em algumas das aplicações menos agressivas da Voice API:
Criação de um sistema de transmissão de mensagens de voz para notificações críticas
Utilizando o IVR e a Voice API para criar uma linha de atendimento para famílias
Iniciando chamadas de Voice a partir de dispositivos vestíveis (para escapar de um encontro ruim)
Criação de uma linha direta para denúncias relacionadas ao Código de Conduta
Compartilhar:
Aaron era um defensor de desenvolvedores na Nexmo. Engenheiro de software experiente e aspirante a artista digital, Aaron costuma ser visto criando coisas com código ou com eletrônica; às vezes, os dois. Geralmente, dá para perceber quando ele está trabalhando em algo novo pelo cheiro de componentes queimando no ar.