• Home /
  • Blog /
  • Crear un Skill en Alexa y conectarlo a un API externo

Crear un Skill en Alexa y conectarlo a un API externo


¿Qué vamos a hacer?

Vamos a crear un skill de Alexa que se pueda conectar con API externo. Este skill funciona con cualquier API que sea público, para hacerlo más interesante todavia nosotros vamos a escribir un pequeño servidor de express que era el API que Alexa va a consumir; pero si no tiene interes en escribir el servidor lo puede descargar del repositorio que esta abajo, o bien puede usar cualquier API externo que conozca.

Para nuestro buen ejemplo crearemos un servidor que en su enpoint ('/') debe devolver un arreglo de 3 cartas con 1 significado:

[
    {
        "name": "magitian",
        "meaning": "you were a messanger",
    },
    {
        "name": "fool",
        "meaning": "you are in the process of building a new cycle",
    },
    {
        "name": "lovers",
        "meaning": "you are on the path of finding a connection, maybe with another person or with yourself."
    }
]

La primera carta representa el pasado, la segunda el presente y la tercera el futuro.

Antes de comenzar:

Pueden el codigo ejemplo de este tutorial en:

Prerequisitos

Una recomendación es separar el skill y el servidor en dos carpetas diferentes /skill /server Antes de comenzar necesitamos:

Para el Alexa Skill:

A medida que necesitemos las otras dependencias lo ire indicando.

Express Server

  1. Entramos a la carpeta /server e iniciamos nuestro proyecto de node.
    npm init
    Ahí llenaremos los datos basicos de nuestro servidor como nos parezca conveniente.
    Dando <b>Enter</b> en nuestro teclado llenaremos la opcion por defecto que nos da npm.
    A mi me gusta llamar el archivo de entrada app.js envez de index.js, pero ambas opciones son validas.
    Al final debemos obtener un archivo <i>package.json</i> donde se guarda toda la infomación que acabamos de llenar y un archivo de entrada <i>index.js</i> es el nombre por defecto pero me referire a este archivo como <i>app.js</i>, ya que Alexa produce un archivo index.js y puede resultar en confuciones.

  2. Una vez terminamos de inicializar el proyecto, debemos instalar la dependencia de express.
    npm install --save express
    El comando --save guarda la dependencia que acabamos de instalar en <i>package.json
    </i>
  3. Entramos a app.js donde vamos a escribir las siguientes lineas:
    let express = require(&#x27;express&#x27;);
    let app = express();

    const port = process.env.PORT || 3000;
    app.listen(port, function(){ console.log(`Corriendo en el puerto ${port}...`) });

    Aqui tenemos un par de explicaciones:
    La primera linea del codigo iguala la variable expres a la dependencia express que instalamos en el paso anterior,
    la variable app la igualamos la variable app a la función express. Creamos una constante port que sera igual a una variable de ambiente o a 3000, por ultimo le decimos a app que inicie la escucha en el puerto definido por la constante port. Pero falta algo importante, definir los controladores para saber que hacer en cada direccion y los modelos que se van a usar.
  4. Creamos una carpeta llamada models/ donde tendremos un archivo llamado cards.js. hasta ahorita nuestra carpeta /server se debe ver asi:

    models/
    --card.js
    node-modules/
    package.json
    app.js

    aqui vamos a crear un pseudo-modelo, es un pseudo modelo porque en este ejemplo no nos vamos a conectar a una base de datos, solo vamos a simular la lectura de una. Para simular la lectura de la base de datos vamos a crear un arreglo llamado cards que contenga el nombre de carta que definimos como tambien 3 significados ( un significado para el pasado, un significado para el presente, y un significado para el futuro). Despues usaremos la instruccion module.exports = cards para permetir que cualquier archivo que requiera cards pueda usarla, nuestro archivo cards deberia verse asi:

    cards = [
    {name: 'fool', meaning: ['you were full of imagination', 'you are in the process of building a new cycle', 'the wisdom of the future is on your grasp']},
    {name:'magitian', meaning: ['you were a messanger','you are in the process of mastering your meaning','you are on the path of concious and unconcious process']},
    {name:'high priestess', meaning: ['you were in the dream world, full of instincts and emotions','you are trying to be in touch with your instinctual powers','you are full of fertility']},
    {name:'emperor', meaning: ['you were in a position of authority and responsabilties','you are full of passion, action and leadership','you are on the path of new beginnings']},
    {name:'hierophant', meaning: ['you were regarded as a spiritual authority','you are full of wisdom and energy','you are on the path of connecting with a higher self']},
    {name:'lovers', meaning: ['you were faced with making an important choice','you are full of balance','you are on the path of finding a connection, maybe with another person or with yourself.']},
    {name:'chariot', meaning: ['you miss both an emotional connection and your home.','you are full of nurturing, caring and nourishment','you are on the path of finding victory.']},
    ]

    module.exports = cards;

  5. Ahora vamos a crear una carpeta llamada /routes y un archivo router.js. Ahora nos vemos asi:

    models/
    --card.js
    routes/
    --router.js
    node-modules/
    package.json
    app.js

    en router debemos volver a requerir 'express' igualandolo a una constante, nuevamente la llame 'express', seguido igualamos otra constante a express.Router() en este caso yo llame a dicha constante 'router', finalmente igualamos una constante más (en mi caso 'cards') al modulo cards que creamos en el paso anterior. El codigo debe quedar asi:

    const express = require('express');
    const router = express.Router();
    const cards = require('../models/cards');

    ahora creamos una funcion que nos permita de forma alteatoria escoger 3 cartas de la variable cards, en la primera carta escogida usaremos meaning[0] porque dicha carta representa el pasado, la siguente carta representa el presente, y la última carta representa el futuro. Dicha función, a la que llame getRandomCards(), la definimos de la siguiente manera:
    function getRandomCards(){
    /* returns random cards */
    let index;
    let selectedCards = [];
    let indexIncluded = [];
    for(i=0; i&lt;=2; i++){
    do{
    index = Math.floor(Math.random() * cards.length);
    }while(indexIncluded.includes(index));
    selectedCards.push({name: cards[index].name, meaning: cards[index].meaning[i]});
    indexIncluded.push(index);
    }
    return selectedCards;
    }

    y ahora necesitamos crear el controlador del router, mostrare el resultado final y lo explicaré:

    router.get(&#x27;/&#x27;, (<i>req</i>, <i>res</i>, <i>next</i>) =&gt; {
    let cardList = getRandomCards();
    res.setHeader(&#x27;Content-Type&#x27;, &#x27;application/json&#x27;);
    res.send(JSON.stringify(cardList));
    });

    router.get maneja todas las peticiones get que lleguen a la direccion definida como primer parametro ( en este caso '/') el segundo paramentro es un callback con 3 parametros los 2 primeros son los que mas usaremos: el parametro req maneja la peticion (o request) al endpoint, el parametro res se encarga de la respuesta que debe dar express y el parametro next es un callback al middleware de la funcion que estamos usando, pero en este caso no hemos definido ningún middleware asi que next no es necesario y solo lo escribo por convención o por si en algún momento es necesario añadir un middleware. Dentro de la funcion igualamos la variable cardList a el retorno de la funcion getRandomCards (la cual ya sabemos que nos va a devolver un arreglo de 3 objetos), con la variable res definimos que el header de la respuesta va a ser 'JSON' y devolvemos el objeto transformado en JSON como respuesta. Finalmente añadimos la exportacion de router:
    module.exports = router;

    y con eso nuestro servidor de express debe devolver como JSON la respuesta que definimos arriba.

Alexa Skill

  1. Creamos un custom skill de Alexa con la instruccion ask new Una vez creada la base de nuestro skill entramos en lamba/custom, ahi crearemos una carpeta llamada modules y un archivo cardRequest.js, el proposito
    de este modulo es de hacer la peticion de GET al servidor que acabamos de crear.

  2. Para hacer las peticiones vamos a instalar dos dependencias request y request-promise con:
    npm install --save request
    npm install --save request-promise

    En el archivo vamos a hacer importación de request-promise y vamos a declarar una variable (a la que llame cardRequest) como un objeto. Se veria asi
    const rp = require('request-promise');
    const cardRequest = {};

    Vamos a añadir una llave al objeto cardRequest e igualarla a una funcion que va a usar la funcion rp() cuyo parametro sera el url de nuestro servidor
    cardRequest.get = function(){ return rp("https://nuestro-url/"); } finalmente agregamos module.exports = cardRequest; gracias a esto codigo podemos llamar a cardRequest.get() para ejecutar la funcion previamente definida.

  3. Entramos a index.js de la carpeta /custom ahi vamos a modificar el LaunchRequestHandler, vamos a transformar handle() en una funcion async y le vamos a pedir que espere el request al servidor quedaria algo asi
    const ReadTarotIntentHandler = {
    canHandle(handlerInput) {
    return handlerInput.requestEnvelope.request.type === 'LaunchRequest'
    || handlerInput.requestEnvelope.request.intent.name === 'AnotherReadIntent';
    },
    async handle(handlerInput) {
    let speechText = 'I don\'t know!';
    await cardRequest.get().then(
    (value) => {
    let cards = JSON.parse(value);
    speechText = `Im gonna draw three cards for you, they will represent your past, your present, and your future respectively.
    Your first card is the ${cards[0].name}, it symbolizes your past, and it means that ${cards[0].meaning}.
    Your second card is the ${cards[1].name}, it symbolizes your present, and it means that ${cards[1].meaning}.
    Your third card is the ${cards[2].name}, it symbolizes your future, and it means that ${cards[2].meaning}.
    if you want a new tarot read you can ask me "tell tarot drawer to give me another read".`
    },
    (error) => {
    speechText = "there was an error.";
    }
    );
    return handlerInput.responseBuilder
    .speak(speechText)
    .withSimpleCard('Your Read:', speechText)
    .getResponse();
    },
    };
    El canHandle define que tanto la instruccion de open como las que esten definidas en AnotherReadIntent ejecutaran la funcion handle quien llama al modulo que definimos en el paso anterior y de ser exitoso modifica el speech text para que lea las cartas que recibimos. Yo en mi caso cambie el nombre de LaunchRequestHandler a ReadTarotIntentHandler, el cambio de nombre es totalmente opcional y se hace tambien hay que cambiar el exports handler para que incluya la nueva funcion:
    exports.handler = skillBuilder
    .addRequestHandlers(
    ReadTarotIntentHandler,...

    El resto del trabajo es simplemente modificar las opciones del skill por si se quieren mas intents y estaremos listos para hacer deploy de nuestro nueco skill.


Return to blog

Contact Us!

Let us know about your software project or the team you need. We will contact you right away.

Let's work together

About Us

We develop solutions using centralized and decentralized technologies.

Our Contacts

Jose Llamas [email protected]

Erick Agrazal [email protected]