https://d226lax1qjow5r.cloudfront.net/blog/blogposts/whamageddon-streaming-last-christmas-into-a-phone-call-dr/Streaming-Last-Christmas-into-a-Phone-Call.png

Transmita la última Navidad en una llamada telefónica con Python

Publicado el May 10, 2021

Tiempo de lectura: 6 minutos

La Navidad es una época cara, pero puedo ayudarte a ahorrar una fortuna en regalos asegurándome de que nadie te hable el día de Navidad enviándolos todos a Whamhalla.

Cada año mis amigos y yo competimos en Whamageddonlas reglas son simples:

  1. El objetivo es pasar el mayor tiempo posible sin escuchar el clásico navideño de WHAM: "Last Christmas".

  2. El juego comienza el 1 de diciembre y termina a medianoche del 24 de diciembre.

  3. Sólo se aplica la versión original

  4. Estás fuera en cuanto reconoces la canción

Cualquiera que haya puesto un pie fuera de casa o encendido la radio en Navidad puede dar fe de lo difícil que es evitar "Last Christmas". A partir de la medianoche del 1 de diciembre, es como si en todas las tiendas la estuvieran repitiendo. La mayoría de la gente juega a la defensiva, evitando los lugares en los que es más probable que suene la canción. Pero nosotros no somos la mayoría: vamos a pasar al ataque.

Los principios de la guerra

Santa driving a tankSanta driving a tank

Aprovechar, mantener y explotar la iniciativa. La acción ofensiva es la forma más eficaz y decisiva de alcanzar un objetivo común claramente definido. Las operaciones ofensivas son el medio por el que una fuerza militar toma y mantiene la iniciativa, al tiempo que mantiene la libertad de acción y logra resultados decisivos. Esto es fundamentalmente cierto en todos los niveles de la guerra.

Podemos ganar el Whamageddon evitando la canción hasta el 25 de diciembre o asegurándonos de que todos nuestros amigos la escuchen antes que nosotros, de modo que seamos la última persona en pie. Tenemos que encontrar la manera de engañarles para que escuchen la canción, sin exponernos nosotros a ella al mismo tiempo. Sin incidentes de fuego amigo, por favor.

El vector de ataque obvio es enviarles un enlace de Youtube de la canción, pero el Rickrolling ha hecho que todo el mundo desconfíe de hacer clic en enlaces desconocidos de Youtube. Necesitamos un canal del que no sospechen.

Golpear al enemigo en un momento o lugar o de una manera para la que no está preparado. La sorpresa puede cambiar decisivamente el equilibrio del poder de combate. Al buscar la sorpresa, las fuerzas pueden lograr un éxito desproporcionado en relación con el esfuerzo realizado. La sorpresa puede consistir en el ritmo, el tamaño de la fuerza, la dirección o ubicación del esfuerzo principal y el momento. El engaño puede aumentar la probabilidad de lograr la sorpresa.

Transmisión de audio en una llamada telefónica

Tanto como Navidad pasada lo impregna todo en esta época del año creo que nunca he contestado al teléfono y lo he oído. Nadie sospecharía que Wham! está llamando.

Vamos a utilizar dos API para conseguirlo, la Voice API de Nexmo para hacer la llamada saliente y reproducir el mp3 a nuestros amigos, y la la API de Spotify para ofrecer una breve muestra de Last Christmas. Ambas API tienen envoltorios de Python para que sea más fácil trabajar con ellas, y vamos a envolverlo todo en una bonita CLI para que podamos eliminar a nuestros amigos con un solo comando. Cuando hayamos terminado va a tener este aspecto:

Screencast of our Voice Application CLScreencast of our Voice Application CL

Primeros pasos

Necesitarás un poco de experiencia con Python para ponerlo en marcha, y al menos la versión 3.6 ya que estamos usando cadenas-f (porque son increíbles) así como un par de cosas más:

  1. El código fuente de código fuente de GitHub

  2. Una Account de Spotify y su propia aplicación de Spotify. Toma nota de tu identificador de cliente y de tu secreto, ya que tendrás que añadirlos a tu .env más tarde

  3. Ngroksi no estás seguro de para qué sirve, deberías leer primero nuestro tutorial sobre ngrok

Vonage API Account

To complete this tutorial, you will need a Vonage API account. If you don’t have one already, you can sign up today and start building with free credit. Once you have an account, you can find your API Key and API Secret at the top of the Vonage API Dashboard.

Configuración

Hay un archivo .env.example en el repositorio, renómbrelo a .env y empieza a rellenar los valores. Para los valores NEXMO_APPLICATION_ID y NEXMO_PRIVATE_KEY tienes que crear una nueva Nexmo Applicationpuede hacerlo a través del Panel de control, recuerde guardar el generado private.key en un lugar seguro y establecer el valor de NEXMO_PRIVATE_KEY a la ubicación de su clave.

El script también tiene varias dependencias; yo recomendaría instalarlas mediante pipenvpero puedes usar algún otro gestor de dependencias de Python si lo prefieres.

pipenv install

Una vez que haya instalado sus dependencias, asegúrese de que ha activado su entorno virtual (sería pipenv shell si utiliza pipenv) y que tu shell contiene las variables de entorno del archivo .env archivo. Por último, tenemos que ser capaces de exponer nuestro script a la Internet pública para que los webhooks de Nexmo puedan alcanzarlo. Para esto usaremos ngrok, no te preocupes por configurar tu tunel, solo asegurate de que ngrok este corriendo.

ngrok http 80

Ya está listo para ejecutar el script.

python pvpwham.py NUMBER

Hay algunas opciones adicionales que puede especificar, ejecute python pvpwham.py --help para obtener más información.

Veamos con más detalle algunas de las partes más interesantes del código.

Validación del número

Asegurarnos de que tenemos un número de teléfono válido es crucial, nada más funciona sin él. Por eso dedicamos algo de tiempo a validar el número y convertirlo al formato correcto. Sin embargo, la gente escribe los números de teléfono de muchas formas raras y maravillosas, así que si no podemos validarlo, les pedimos que se ciñan al E.164 e incluso les proporcionamos un enlace con más información antes de pedirles que vuelvan a intentarlo.

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 un MP3 de la canción

No necesitamos la canción entera; unos segundos deberían bastar para enviar a alguien al Whamhalla. El avance de 30 segundos de Spotify es ideal. El script utiliza por defecto "Last Christmas", pero puedes configurarlo con la opción --track opción.

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]

Nuestro servidor

Tenemos varios webhooks Nexmo diferentes que tenemos que manejar.

URL de respuesta: se llama cada vez que alguien responde a la llamada y contiene una lista de acciones que Nexmo debe realizar. En nuestro caso, vamos a decirle a Nexmo que grabe la llamada, que utilice texto a voz para leer una breve advertencia al usuario (está desactivado por defecto, pero puede activarse mediante la opción --delay opción)y, por último, que transmita nuestra vista previa de Spotify en la llamada

@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_file

URL de grabación - Una vez que el usuario ha colgado el teléfono y la llamada se ha completado Nexmo golpea este webhook con los detalles de la grabación para que podamos descargar el mp3 y escuchar la consternación de nuestro amigo al darse cuenta de lo que ha sucedido.

NB: Grabamos todo, incluido el audio que hemos transmitido en la llamada. No querrás enviarte también accidentalmente al Whamhalla. Puedes modificar el guión para utilizar grabaciones divididas para evitar esto.

@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 - Esto es puramente informativo. El script actualiza el terminal con el último estado que recibe a través del 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"

Limpieza

Cuando se completa la llamada, tenemos un gancho on_end_request que hace algo de limpieza. Apagamos nuestro servidor Cherrypy y matamos nuestro 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)

Otros comandos divertidos

En Spotify se pueden encontrar cosas muy raras si eres lo bastante creativo.

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'

¿Y ahora qué?

Dirigir cada operación militar hacia un objetivo claramente definido, decisivo y alcanzable. El fin militar último de la guerra es la destrucción de la capacidad y voluntad de lucha del enemigo.

Incluso si no consigues eliminar a todos tus amigos, con suerte los supervivientes estarán tan ansiosos cada vez que suene el teléfono que se rendirán. Entrando en su centro comercial local para sentarse y esperar lo inevitable.

Después de aplastar a la oposición, echa un vistazo a algunos de los usos menos militantes de la Voice API:

Compartir:

https://a.storyblok.com/f/270183/150x150/a3d03a85fd/placeholder.svg
Aaron BassettAntiguos alumnos de Vonage

Aaron era un defensor de los desarrolladores en Nexmo. Ingeniero de software experimentado y aspirante a artista digital, Aaron suele crear cosas con código o electrónica; a veces ambas cosas. Cuando está trabajando en algo nuevo, suele percibir el olor a componentes quemados en el aire.