Automatizando lectura de mensajes de Discord con Python y Alexa Skills
Tags: python, azure, discord, alexa
Published: 2021-12-26 10:15:16

#### Automatización
La automatización consiste en utilizar la tecnología para realizar tareas casi sin necesidad de realizar acciones humanas.
#### Alexa como asistente inteligente

Un asistente inteligente es un software que realiza tareas y ofrece servicios a un individuo, ayuda en las tareas en sistemas automatizando y realizando acciones con la mínima interacción hombre-máquina. Una persona se comunica usando la voz o texto y el asistente lo procesa, identifica, interpreta, ejecuta y responde utilizando la voz o medios visuales.

Alexa es el servicio  de voz ubicado en la nube de Amazon disponible en dispositivos Amazon y dispositivos con Alexa integrada, se encuentran dispositivos con solo altavoces como Echo, Echo Dot, dispositivos con pantalla cómo Echo Show, Fire Stick TV y aplicaciones en Android, Windows, y muchos otros dispositivos más incluido televisores smart.

#### Skills de alexa y Rutinas

Una Skill son las habilidades que posee Alexa para realizar tareas, similar a una aplicación en un smartphone es una Skill de Alexa, a través de las Skills se permite realizar ciertas tareas, interactuar con otros dispositivos, estas habilidades son realizadas por  desarrolladores y compañías  para permitir a Alexa integrarse con otros Sistemas y dispositivos.

Son proyectos de software o funciones que se alojan en alguna nube y se ejecutan según la demanda de los usuarios, cada vez que se invoca la habilidad específica.

Las rutinas son un conjunto de reglas y ejecuciones que permiten ejecutar diferentes tareas que se programan a través de la Aplicación de Alexa, enlazando características de los diferentes dispositivos, skills, tiempo de ejecución de tareas, todas ellas permiten crear rutinas, tan simple como encender una alarma sonora a una hora específica, controlar la temperatura de un ambiente utilizando un termómetro y un ventilador, o recordarte comprar un objeto al llegar a un lugar, escuchar las noticias de diferentes proveedores con solo decirle “Quiero escuchar noticias”.

Las rutinas más las Skills permiten sacar gran parte del potencial del asistente inteligente, a tal punto de influir en la vida misma y las acciones del dia a dia de cada persona, facilitando el trabajo, ayudando a recordar o informar, ayudando a mantenerse en salud óptima, divertirse y disfrutar de un momento de ocio. 

#### Serverless

La computación sin servidor o Serverless es un modelo en el cual los proveedores de cloud (AWS, Azure, GCP, etc.) son responsables de ejecutar un fragmento de código mediante la asignación dinámica de recursos. Cobrando solo por la cantidad de recursos utilizados para ejecutar el código. El código se envía al proveedor en la nube para ejecución en forma de una función, serverless puede ser denominado como “Funciones como servicio” o FaaS. 

La computación sin servidor no significa que no hay un servidor en el cual se ejecuta sino que los proveedores se encargan del mantenimiento y solo se cobra por la ejecución de las funciones, tradicionalmente se suele pagar  el mantenimiento de máquinas virtuales, plataformas como servicio aun cuando no se encuentran en uso por los clientes a través de solicitudes HTTP o Sockets y se encuentran en un estado Idle o de espera, con Serverless cuando no hay llamadas de ejecución, solicitudes HTTP o CRON, no hay recursos destinados a mantener activa la función en estado de espera, el costo se basa en la cantidad de recursos consumidos en cada ejecución por función.

Entre los más populares se encuentra:
- Amazon Web Services: AWS Lambda
- Microsoft Azure: Azure Functions
- Google Cloud: Cloud Functions

#### AWS Lambda vs Azure Functions (Diferencias)

- Configurabilidad
AWS Lambda, necesita configurar la asignación máxima de memoria entre 128 mb a 3 GB, se ejecuta bajo Amazon Linux.
Azure Functions, posee un plan único para todas las funciones, 1.5 GB de memoria, es posible ejecutar en Windows o Linux
- Lenguajes
AWS Lambda, permite JavaScript, Java, Python, C#, F#, PowerShell, Go y Ruby
Azure Functions, permite JavaScript, Java, Python, C#, F#, y PowerShell, TypeScript
- Costo
El precio de ambos es de aproximada 0.20 USD por millón de solicitudes y 16 dólares por millón de GB/s
AWS Lambda cobra por la capacidad total de memoria aprovisionada.
Azure Functions mide el consumo de memoria promedio real de las ejecuciones.

#### Iniciando el proyecto

Crear el servidor de Discord (Si no se lo tiene)

![Crear Servidor Discord](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1wewbk9ai1vcnsz8l457.gif)

Crear el canal de texto de noticias a utilizar, si no se tiene, puede ser público o privado.
Para el acceso al canal se tiene que obtener su ID del canal el cual se obtiene desde el Discord, este dato será nuestro DISCORD_CHANNEL_ID con el que accederemos a un canal en específico para noticias.


![Crear un canal](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/45bqp7w8zs7q6zrl4af3.png)


![Obtener el ID del Canal](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/34d2gx4e2zxoxjdvrla3.png)

En caso de que no aparezca la opción para “Copiar ID” se debe activar desde Ajustes del Usuario > Avanzado > Modo Desarrollador

Se necesita crear una aplicación de discord para conectarse con el servidor, desde [Discord Developer](https://discord.com/developers/applications) , en la cual crearemos una aplicación.


![Crear Aplicación](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/59g9c6ii5njgtp4bj5ql.png)

![Información general de la Aplicación](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9kwa70zihuocmdzj5apk.png)

Una vez creada la aplicación debemos generar un bot dentro la aplicación el cual tendrá la capacidad de conectarse a los canales de Discord y obtener los mensajes.


![Adicionar el Bot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oxdlxjmf562iz01528mv.png)

Al generar el bot podemos obtener su Token de acceso con el cual podremos acceder mediante el bot, el cual lo utilizaremos más adelante.


![Detalles del Bot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zbjdaltviwammimf5jgo.png)

Para agregar el bot de prueba a su servidor de Discord puede utilizar el siguiente enlace modificando los parámetros de acuerdo a los permisos que requiera.

https://discord.com/oauth2/authorize?client_id=APPLICATION_ID&permissions=PERMISSION_INTEGER&scope=bot

APPLICATION_ID lo podremos obtener de la información general de la Aplicación, reemplazamos en la URL de autorización.

PERMISSION_INTEGER es el número referencial a los permisos que tendrá el bot  usamos el 66560 para la lectura de mensajes.


![Permisos del Bot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u91zp7pwung5wsos2xy6.png)

Para más información de los alcances existentes puede visitar: https://discord.com/developers/docs/topics/oauth2#shared-resources-oauth2-scopes

 
![Adición del Bot al Servidor](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3bh1526riq9ob9uxaxrp.png)

Una vez agregado el bot al Servidor ya podemos realizar la lectura de los mensajes, en caso de que se encuentre en un canal privado primero se debe agregar al bot al canal para poder acceder a los mensajes.

Consumo del API de Discord para obtención de los mensajes de un canal

Teniendo ya el bot configurado podemos realizar la lectura de los mensajes utilizando solicitudes HTTP para lo cual podemos realizar desde python de la siguiente manera.

```py
import requests
url = "https://discordapp.com/api/channels/DISCORD_CHANNEL_ID/messages?limit=10"
payload={}
headers = {'Authorization': 'Bot BOT_TOKEN'}
response = requests.request("GET", url, headers=headers, data=payload)
print(response.text)
```
#### Crear la Skill

Ingresamos con nuestra cuenta a https://developer.amazon.com/alexa/console/ask desde el cual procederemos a crear nuestra Skill

 
![Crear Skill](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o526wxr6oyuak29d2bmv.png)

Al crear nuestra Skill tenemos que configurar su nombre, el idioma primario para el que utilizaremos el Español, el modelo para el Skill será Custom ya que será proporcionado por nosotros, y la ubicación de nuestro backend que también la proporcionaremos nosotros mismos.


![Configurar Skill](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p6q21zdqvlg30s4c404h.png)

Para la demostración utilizaremos el Template inicial el cual contiene funcionalidades básicas


![Template Skill](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3cdwnhttohx1rvwadqki.png)

Una vez terminado de construir ya tenemos la Skill inicial en la cual nosotros podemos realizar la configuración de los Intents, Samples y Slots para que Alexa pueda entender nuestra Skill y realizar un correcto funcionamiento.

#### Nombre de Invocación

Lo primero que debemos revisar es el nombre de invocación de nuestra Skill, es el conjunto de palabras que permite a Alexa identificar y ejecutar nuestra Skill, ingresando a Skill Invocation Name cambiamos el nombre para la invocación, y guardamos nuestro modelo.


![Nombre Invocación Skill](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rbmx558mv00w3438vz1k.png)

#### Definición de Intents, samples y slots

Los Intents representan a las acciones que cumplen con la solicitud hablada de un usuario, para lo cual crearemos nuestro Intent desde el menú de Intents y nuestro nuevo intent será “NoticiasIntent”.


![Intents de la Skill](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r7ff0l7dxlnfxo2nd9j6.png)

De manera integrada en cada Skill tenemos  4 Intents  que son para Cancelar, Ayuda, Detener ejecución y navegar entre nuestra Skill.

Posterior a crear nuestro intent debemos adicionar los Samples que representan la forma en la que los usuarios llaman a nuestro intent desde la Skill, y guardamos nuestro modelo


![Samples de la Skill](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qadqe4u9rhg70vww36vp.png)

Los Slots son argumentos que nos permiten obtener parámetros del usuario que se extraen de los Intent y se envían como datos adicionales. En nuestro caso utilizaremos un slot para obtener un número y obtener un límite de mensajes de acuerdo a necesidad del usuario.


![Slot de la Skill](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9vagur3lzrugh37oxciz.png)

Una vez adicionado el Slot podemos agregar Samples que contengan nuestro Slot y tener llamadas con cantidades específicas de mensajes, quedándonos de la siguiente manera nuestro Intent con sus Samples y Slot!, y guardamos nuestro modelo Final


![Intent Completo de la SKill](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4sfitk5jnrzz5bwx48cc.png)


Hasta este punto ya tenemos la estructura que tendrá nuestra Skill y cómo será invocado mediante voz, ahora tenemos que construir nuestro Backend, una vez procesado y analizado la comunicación con el usuario se debe comunicar al Backend de la Skill que la tendremos alojada en Azure Functions

#### Creación de una Función con VS Code

Para crear el Backend primero abriremos VS Code e instalaremos la extensión de Azure Functions, esto para crear y publicar más fácilmente nuestras funciones https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurefunctions


Desde la pestaña de Azure se crea un nuevo Proyecto en la carpeta que contendrá nuestras Funciones.


![Crear Función](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tm2iq0py284vo1535gue.png)

Seleccionamos el Lenguaje “Python”


![Seleccionar lenguaje](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wwi81hgoimgld9ukh4e6.png)

El entorno virtual a utilizar, se puede realizar un skip si no se utilizara entorno virtual, pero es recomendado siempre utilizar un entorno virtual.


![Seleccionar entorno virtual](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iiplet3mlaiqm3a31wpl.png)

Seleccionamos el tipo de Template de nuestro proyecto, al ser un Backend de la Skill tiene el formato de Solicitud HTTP Trigger


![Template de la Función](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ifw8971yruq3r8d6whj4.png)

Como paso siguiente le proporcionamos el nombre a una función, en nuestro caso será “PotatoNoticiasTrigger”


![Nombre de la función](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/38c0g0318har0yu9pqx9.png)

Para este ejemplo utilizaremos un nivel de autorización anónimo


![Autenticación de la función](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e7isavivqzklodv7k2wi.png)

Por último lo abrimos en el mismo Workspace o en una nueva ventana


![Ventana para el proyecto](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hms22d8cder94dbsumkf.png)


Con este último paso se realiza el generado del proyecto y tendremos una estructura similar de la siguiente forma:
```sh
.
├── .funcignore
├── .gitignore
├── host.json
├── local.settings.json
├── PotatoNoticiasTrigger
│   ├── function.json
│   ├── __init__.py
│   └── sample.dat
├── requirements.txt
└── .vscode
    ├── extensions.json
    ├── launch.json
    ├── settings.json
    └── tasks.json

```

Para probar podemos ejecutar desde el proyecto la función
```sh
func host start
```


![Prueba Función](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6vnq2k3041qsq3btjq6n.png)

Al momento de iniciar se levanta la instancia  de Azure Functions Core Tools, puede suceder una excepción 

```sh
Value cannot be null. Parameter name: Provider error on func start
```

Puede ser causada por la versión de la herramienta por lo cual una forma es verificar la versión en el archivo host.json
Para el proyecto actual se utiliza la version "[3.3.0, 4.0.0)", 
```json
"extensionBundle": {
    "id": "Microsoft.Azure.Functions.ExtensionBundle",
    "version": "[3.3.0, 4.0.0)"
  }
```


Adicionamos a las librerías de requerimientos los siguientes para el diseño de la Skill y consumo de solicitudes

requirements.txt
```txt
requests
ask-sdk-core
ask-sdk-webservice-support
```

Instalaremos las librerías utilizando

```sh
pip install -r requirements.txt
```

Ahora podemos implementar la lógica de nuestra Skill sobre la función. Para lo cual primero empezaremos por añadir un archivo de configuración, para obtener en formato de variables de entorno los Keys e ID’s para proteger esa información de publicarla libremente.

config.py
```py
import os
ALEXA_SKILL_ID = os.getenv("ALEXA_SKILL_ID", "")
DISCORD_CHANNEL_ID = os.getenv("DISCORD_CHANNEL_ID", "")
DISCORD_BOT_TOKEN = os.getenv("DISCORD_BOT_TOKEN", "")
```
Estos 3 valores son los que usamos desde los distintos lugares para acceder a nuestros demás servicios.

ALEXA_SKILL_ID lo podemos obtener desde la consola de Alexa https://developer.amazon.com/alexa/console/ask  en la cual podemos copiar nuestro Skill ID

DISCORD_CHANNEL_ID ya lo habíamos obtenido anteriormente de la creación del canal

DISCORD_BOT_TOKEN al igual lo obtuvimos con anterioridad al crear el BOT de Discord

#### Obtención de los N mensajes desde Discord

Ahora crearemos el método en python que nos permitirá obtener los “N” últimos mensajes de discord

discord.py
```py
from typing import List
import requests
import json
from PotatoNoticiasTrigger.config import DISCORD_CHANNEL_ID, DISCORD_BOT_TOKEN
def get_n_messages(size: int = 5):
    # type: (int) -> List
    payload = {}
    url = "https://discordapp.com/api/channels/{0}/messages?limit={1}".format(
        DISCORD_CHANNEL_ID, size)
    headers = {
        'Authorization': 'Bot {0}'.format(DISCORD_BOT_TOKEN)
    }
    response = requests.request("GET", url, headers=headers, data=payload)
    if (response.status_code == 200):
        data = response.json()
        return data
    else:
        return None
```


#### Implementación de las funciones básicas para un Skill en Python

Ahora debemos implementar las funciones básicas de la Skill e incluir todos los Intents, el que vamos a implementar “NoticiasIntent” como los por defecto como de excepciones para poder responder a posibles fallas de ejecución. 

Los Intents son manejados en formato de clase, en el cual definimos de tipo AbstractRequestHandler, un método “can_handle” con el nombre del Intent a manejar en esa clase, y un método “handle” en el cual se realiza la ejecución de la acción para ser devuelta a Alexa a través de la Skill ya sea en voz o en texto visual.

##### Invocación
default_intents.py
```py
class LaunchRequestHandler(AbstractRequestHandler):
    def can_handle(self, handler_input):
        return is_request_type("LaunchRequest")(handler_input)
 
    def handle(self, handler_input):
        print("Ejecucion de la Skill.")
        speech_text  = "Bienvenido a Potato Noticias, puedes decir últimas noticias"
        handler_input.response_builder.speak(speech_text).set_card(
            SimpleCard("Potato Noticias", speech_text)).set_should_end_session(False)
        return handler_input.response_builder.response
```
##### Ayuda
default_intents.py
```py
class HelpIntentHandler(AbstractRequestHandler):
    def can_handle(self, handler_input):
        # type: (HandlerInput) -> bool
        return is_intent_name("AMAZON.HelpIntent")(handler_input)
 
    def handle(self, handler_input):
        # type: (HandlerInput) -> Response
        print("Ejecucion Intent Ayuda")
        speech_text = "Puedes decir últimas noticias, últimos 2 mensajes"
        handler_input.response_builder.speak(speech_text).ask(speech_text).set_card(
            SimpleCard("Potato Noticias - Ayuda", speech_text))
        return handler_input.response_builder.response
```
##### Cancelar o Detener
default_intents.py
```py
class CancelAndStopIntentHandler(AbstractRequestHandler):
    def can_handle(self, handler_input):
        # type: (HandlerInput) -> bool
        return is_intent_name("AMAZON.CancelIntent")(handler_input) or is_intent_name("AMAZON.StopIntent")(handler_input)
 
    def handle(self, handler_input):
        # type: (HandlerInput) -> Response
        print("Ejecución Intent Cancel o Stop")
        speech_text = "Hasta luego Atentamente Potato Noticias!"
        handler_input.response_builder.speak(speech_text).set_card(
            SimpleCard("Potato Noticias", speech_text)).set_should_end_session(True)
        return handler_input.response_builder.response
```
##### Terminar Sesión
default_intents.py
```py
class SessionEndedRequestHandler(AbstractRequestHandler):
    def can_handle(self, handler_input):
        # type: (HandlerInput) -> bool
        return is_request_type("SessionEndedRequest")(handler_input)
 
    def handle(self, handler_input):
        # type: (HandlerInput) -> Response
        # any cleanup logic goes here
        return handler_input.response_builder.response
```
##### Manejo de Excepciones
default_intents.py
```py
class AllExceptionHandler(AbstractExceptionHandler):
    def can_handle(self, handler_input, exception):
        # type: (HandlerInput, Exception) -> bool
        return True
 
    def handle(self, handler_input, exception):
        # type: (HandlerInput, Exception) -> Response
        print(exception)
        speech = "Lo siento, No pude procesarlo. Puedes intentarlo nuevamente o decir Ayuda"
        handler_input.response_builder.speak(speech).ask(speech)
        return handler_input.response_builder.response
```

El archivo final default_intents quedaria de la siguiente manera
```py
from ask_sdk_core.dispatch_components import AbstractRequestHandler, AbstractExceptionHandler
from ask_sdk_core.utils import is_request_type, is_intent_name
from ask_sdk_core.handler_input import HandlerInput
from ask_sdk_model import Response
from ask_sdk_model.ui import SimpleCard


class LaunchRequestHandler(AbstractRequestHandler):
    def can_handle(self, handler_input):
        return is_request_type("LaunchRequest")(handler_input)

    def handle(self, handler_input):
        print("Ejecucion de la Skill.")
        speech_text = "Bienvenido a Potato Noticias, puedes decir últimas noticias"
        handler_input.response_builder.speak(speech_text).set_card(
            SimpleCard("Potato Noticias", speech_text)).set_should_end_session(False)
        return handler_input.response_builder.response


class HelpIntentHandler(AbstractRequestHandler):
    def can_handle(self, handler_input):
        # type: (HandlerInput) -> bool
        return is_intent_name("AMAZON.HelpIntent")(handler_input)

    def handle(self, handler_input):
        # type: (HandlerInput) -> Response
        print("Ejecucion Intent Ayuda")
        speech_text = "Puedes decir últimas noticias, últimos 2 mensajes"
        handler_input.response_builder.speak(speech_text).ask(speech_text).set_card(
            SimpleCard("Potato Noticias - Ayuda", speech_text))
        return handler_input.response_builder.response


class CancelAndStopIntentHandler(AbstractRequestHandler):
    def can_handle(self, handler_input):
        # type: (HandlerInput) -> bool
        return is_intent_name("AMAZON.CancelIntent")(handler_input) or is_intent_name("AMAZON.StopIntent")(handler_input)

    def handle(self, handler_input):
        # type: (HandlerInput) -> Response
        print("Ejecucion Intent Cancel o Stop")
        speech_text = "Hasta luego Atentamente Potato Noticias!"
        handler_input.response_builder.speak(speech_text).set_card(
            SimpleCard("Potato Noticias", speech_text)).set_should_end_session(True)
        return handler_input.response_builder.response


class SessionEndedRequestHandler(AbstractRequestHandler):
    def can_handle(self, handler_input):
        # type: (HandlerInput) -> bool
        return is_request_type("SessionEndedRequest")(handler_input)

    def handle(self, handler_input):
        # type: (HandlerInput) -> Response
        # any cleanup logic goes here
        return handler_input.response_builder.response


class AllExceptionHandler(AbstractExceptionHandler):
    def can_handle(self, handler_input, exception):
        # type: (HandlerInput, Exception) -> bool
        return True

    def handle(self, handler_input, exception):
        # type: (HandlerInput, Exception) -> Response
        print(exception)
        speech = "Lo siento, No pude procesarlo. Puedes intentarlo nuevamente o decir Ayuda"
        handler_input.response_builder.speak(speech).ask(speech)
        return handler_input.response_builder.response

```
#### Implementación de la lógica para el Intent de solicitud de N mensajes

Hasta este punto ya hemos implementado los Intents básicos que debe poseer una Skill para facilitar a los usuarios su administración y manejo, ahora nos toca implementar de la misma manera nuestro propio Intent :D

Primeramente definiremos un generate_reponse que recibe un array de mensajes y los convierte en texto para ser escuchado y texto para ser leído desde un dispositivo.

noticias_intent.py
```py
def generate_response(messages):
    # type: (List) -> Tuple[str,str]
    speak_out = ""
    speak_out_visible = ""
    itemCount = 1
    for message in messages:
        speak_out += 'Mensaje {0}: <break time="0.5s"/> {1}. <break time="1s"/>'.format(
            itemCount, message.get("content", ""))
        speak_out_visible += '{0}: {1}.\n'.format(
            itemCount, message.get("content", ""))
        itemCount += 1
    return speak_out, speak_out_visible
```
En el caso de speak_out se añade la etiqueta break esto hace referencia al SSML (Speech Synthesis Markup Language), que es un estándar que permite a los asistentes de voz añadir pausas, efectos especiales de voz como susurro, o la pronunciación de palabras en un idioma extranjero de forma correcta


Después creamos el Handler para nuestro NoticiasIntent, en el cual accederemos a discord y obtendremos los mensajes les daremos un formato para ser escuchados y vistos y sean devueltos hacia el asistente.

noticias_intent.py
```py
class NoticiasIntentHandler(AbstractRequestHandler):
    def can_handle(self, handler_input):
        # type: (HandlerInput) -> bool
        return is_intent_name("NoticiasIntent")(handler_input)
 
    def handle(self, handler_input):
        # type: (HandlerInput) -> Response
        cantidad = get_slot_value(handler_input, "cantidad")
        print(cantidad)
        if cantidad is not None:
            messages = get_n_messages(size=cantidad)
        else:
            messages = get_n_messages()
        if messages is None or len(messages) == 0:
            speak_output = "No se encontraron mensajes"
            visual_output = "No se encontraron mensajes :("
        else:
            speak_output,visual_output = generate_response(messages)
            speak_output += " Esos son todos los mensajes!."
            visual_output += " Esos son todos los mensajes!."
        handler_input.response_builder.speak(speak_output).set_card(
            SimpleCard("Potato Noticias", visual_output)).set_should_end_session(True)
        return handler_input.response_builder.response
```

Hasta este punto ya tenemos los intents y nos faltaria integrarlo en el init de la función y realizar pruebas :D 

#### Integración de los intents en el init de la función

Reemplazamos todo el contenido de nuestro con lo siguiente, que integra tanto nuestros intents  por defecto como el Intent custom para lectura de discord.

__ init __.py
```py
import json
import azure.functions as func
from ask_sdk_core.skill_builder import SkillBuilder
from ask_sdk_webservice_support.webservice_handler import WebserviceSkillHandler
from PotatoNoticiasTrigger.default_intents import AllExceptionHandler, CancelAndStopIntentHandler, HelpIntentHandler, LaunchRequestHandler, SessionEndedRequestHandler
from PotatoNoticiasTrigger.noticias_intent import NoticiasIntentHandler
from PotatoNoticiasTrigger.config import ALEXA_SKILL_ID
 
def main(req: func.HttpRequest) -> func.HttpResponse:
    skill_builder = SkillBuilder()
    skill_builder.skill_id = ALEXA_SKILL_ID
    # Default Intents
    skill_builder.add_request_handler(LaunchRequestHandler())
    skill_builder.add_request_handler(HelpIntentHandler())
    skill_builder.add_request_handler(CancelAndStopIntentHandler())
    skill_builder.add_request_handler(SessionEndedRequestHandler())
    skill_builder.add_exception_handler(AllExceptionHandler())
    # Custom Intents
    skill_builder.add_request_handler(NoticiasIntentHandler())
 
    webservice_handler = WebserviceSkillHandler(skill=skill_builder.create())
    response = webservice_handler.verify_request_and_dispatch(req.headers, req.get_body().decode("utf-8"))
    return func.HttpResponse(json.dumps(response),mimetype="application/json")
```

Ahora podemos probar el Intent y conectarlo a la Skill a través de Ngrok :D

Primeramente ejecutamos nuestra Función utilizando:
```sh
func host start
```

Esta llamada nos genera una salida similar en la cual nos muestra la salida de nuestra Función en local similar a:
```
http://localhost:7071/api/PotatoNoticiasTrigger
```
Ahora gracias a la herramienta [Ngrok](https://ngrok.com/)  que nos permite abrir tuneles publicos hacia nuestros Endpoints locales ejecutaremos en otra terminal
```sh
ngrok http 7071
```
Que nos generará una dirección temporal para que conectemos nuestro EndPoint de la siguiente manera:
```sh
Forwarding                    http://2dba-200-87-92-9.ngrok.io -> http://localhost:7071
Forwarding                    https://2dba-200-87-92-9.ngrok.io -> http://localhost:7071
```
Utilizando la dirección HTTPS volvemos a la consola de Amazon y nos dirigimos a la parte de Endpoint en Default Region y añadimos nuestra dirección Ngrok más la ruta del Trigger teniendo algo similar a:
```
https://2dba-200-87-92-9.ngrok.io/api/PotatoNoticiasTrigger
```

Seleccionamos la opción para el certificado SSL “My development endpoint is a sub-domain of a domain that has a wildcard certificate from a certificate authority”


![Endpoint Skill](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/odn7ofexduahneyzw9ix.png)

Guardamos el Endpoint, ahora tenemos que compilar para lo cual nos dirigimos hacia el menú principal y seleccionamos la Opción Build Model para construir el modelo de la Skill.


![Construir Modelo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yjif7nadv8z4vt39q71p.png)


Esperamos unos minutos a que termine la construcción del modelo.

![Finalizado construcción](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pu62vc5r5ruanzc2pcnv.png)


Pruebas de funcionamiento de la Skill integrada con el Endpoint Local

Desde la pestaña de Test podemos probar nuestra Skill apuntando hacia el Backend local en nuestro equipo. Habilitamos la Skill en modo Development y ya podemos probar


![Pestaña de prueba Skill](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ftm4bxtsie6ii7soaxng.png)


![Prueba de Ejecución](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jczeq8h1fjelfc8h1e1x.png)


![Prueba Visual](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/slwmij29hcuk3ozeukgk.png)

#### Publicación en Azure del Backend de la Skill


Hemos logrado hacer funcionar la Skill y que nos responda desde Local ahora podemos publicarlo en Azure :D y actualizar el EndPoint y volver a compilar nuestro modelo.
Desde VS Code seleccionamos la Opción Deploy to Function App


![Deploy Function App](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7rmgjg51d4atq0cz2bno.png)

Seleccionamos la Suscripción de Azure que tengamos disponible, para usuarios nuevos pueden habilitar una suscripción de prueba y para estudiantes habilitar una suscripción de tipo estudiantil si se los permite sus cuentas institucionales.


![Seleccionar Suscripción](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/045n15jxe5nfg9d6rgds.png)

Seleccionar una nueva función en Azure


![Seleccionar nueva función](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c48orm865jjeujmlzyp0.png)

Ingresamos el  nombre de nuestra  nueva función


![Nombre de la función](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mgt0i3e67pp1ifrr2pi4.png)

Seleccionamos la pila de ejecución que se recomienda que sea coincidente con la que tenemos en local


![Runtime de la función](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0qxdae7ab0beq8qwgia1.png)

Seleccionamos la región donde se aloja nuestra función, en nuestro caso WEST US y se procede a crear nuestra función, esperamos unos minutos mientras se generan los servicios.


![Implementación de la función](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rjvtujq460llbevhtbwz.png)
Tenemos que configurar nuestras variables de entorno con nuestros KEYS y ID Seleccionamos la función y abrimos en Portal


![Open in Portal](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hx22vrp73h0eiqu6iefv.png)

Desde las configuraciones en Azure Añadimos  3 Configuraciones de Aplicación con el mismo nombre de nuestras variables de entorno



![Añadir variables de entorno](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qrifr45d27nvin7pr17w.png)

Una vez adicionado nuestras variables de entorno procedemos a guardar donde se reiniciará la instancia de la función 


![Guardar Variables de Entorno](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3obud00vwmvw4j13pakb.png)

Ahora desde la parte de funciones podemos obtener la URL de nuestro nuevo ENDPOINT similar a
```
https://potatonoticias.azurewebsites.net/api/PotatoNoticiasTrigger
```

el cual actualizaremos en el Endpoint en el portal de Alexa y volveremos a construir el modelo esperemos que termine y podemos probar nuestra Skill


De esta manera podemos concluir el contenido, consumir un canal de Discord, pasar por una Skill de Alexa  y publicar el backend de nuestra función en Azure, todo un conjunto de elementos integrados para automatizar y permitir a las personas escuchar potato noticias desde cualquier dispositivo con Alexa.

Muchas gracias!!

>Un agradecimiento a la comunidad de Python La Paz por permitirme dar una charla sobre esto, síganlos en sus redes sociales [Facebook](https://www.facebook.com/PythonLaPaz) y [Twitter](https://twitter.com/PythonLaPaz) y pregunten por su servidor de Discord, para conocer más sobre futuras charlas y eventos, también pueden ser expositores y enseñarnos su conocimiento para crecer con esta bonita comunidad.


##### El repositorio público en Github lo pueden encontrar aqui del proyecto [Potato Noticias](https://github.com/jevillanueva/PotatoNoticias)

##### La presentación se encuentra disponible en el siguiente enlace [Presentación](https://docs.google.com/presentation/d/18hzMH73g16cx4x7exOMzi_ZGhM48Q19TW5DmXUywgik/edit?usp=sharing)

Algunos enlaces en cuanto a contenido sobre todo lo expuesto.

#### Bibliografías
https://www.redhat.com/es/topics/automation
https://blog.grupocajamar.com/que-son-los-asistentes-virtuales-inteligentes/
https://developer.amazon.com/es-ES/alexa
https://alexaecho.es/las-mejores-ideas-de-rutina-y-habilidades-de-alexa-para-automatizar-tu-vida/ 
https://serverless-stack.com/chapters/es/what-is-serverless.html
https://es.wikipedia.org/wiki/Serverless_computing
https://iamondemand.com/blog/aws-lambda-vs-azure-functions-ten-major-differences