https://d226lax1qjow5r.cloudfront.net/blog/blogposts/restaurant-is-now-delivering-a-facebook-bot-in-node-js/restaurant-closed_1200x600.png

Restaurant Is Now Delivering: un bot de Facebook en Node.js

Publicado el October 13, 2021

Tiempo de lectura: 14 minutos

Introducción

A menudo, cuando estoy programando, me entra hambre. Y todo el mundo sabe que los mejores desarrolladores son perezosos, así que en lugar de ir de compras y cocinar y y limpiar, suelo usar una aplicación de comida a domicilio y simplemente pido una sabrosa comida. El problema es que, con demasiada frecuencia, mis restaurantes favoritos están fuera de línea. A veces están cerrados, otras veces están demasiado ocupados y dejan de aceptar pedidos por Internet. Así que me veo obligada a esperar y acordarme de comprobar si vuelven a estar en línea, y luego abrir la aplicación y comprobar si vuelven a estarlo. Y a veces volver a comprobarlo una y otra vez. Es una verdadera injusticia 😆.

Tiene que haber una forma mejor y más innovadora. Por suerte, hace poco descubrí que mi aplicación favorita de reparto de comida, Wolt, tiene una API que me avisa si un restaurante está en línea. Así que, utilizando la API de Messages API de Vonage, he creado un bot para Facebook Messenger que me avisará cuando mi restaurante favorito vuelva a estar online.

(Este ejemplo está construido alrededor de un caso de uso de entrega de comida, pero el mismo código puede ser reutilizado para crear un bot de Facebook que alerte a los usuarios de cualquier cambio de caso booleano).

Requisitos previos

Esta aplicación requiere lo siguiente:

Pseudocódigo:

Antes de empezar a programar, me gusta pensar en la lógica. Vamos a desglosar los pasos necesarios para construir esta aplicación:

  1. Configurar un servidor Express

  2. Conéctate a Vonage Messages API Sandbox

  3. Llamar a la API Wolt para un restaurante solicitado

  4. Compruebe si el restaurante recibido está en línea

  5. Enviar un mensaje al usuario en función del estado del restaurante

  6. Si el restaurante no está en línea, añádalo a una lista de restaurantes sin conexión

  7. Comprueba continuamente la lista de restaurantes sin conexión para ver si hay algún cambio de estado

  8. Si un restaurante se conecta, envía un mensaje al usuario y lo elimina de la lista de restaurantes desconectados

Configurar nuestro proyecto

Crear una aplicación de nodo

Empecemos por crear nuestro proyecto:

mkdir isItDelivering

A continuación, muévase dentro del directorio del proyecto:

cd isItDelivering

Inicializa el proyecto de nodos:

npm init

Instala los paquetes Node necesarios:

npm install -s @vonage/server-sdk@beta express dotenv got lokijs

Y por último, crear los archivos donde vivirá nuestro código:

touch index.js .env

Verás que estamos utilizando el SDK de Vonage SDK de servidor de nodos para acceder a la Messages API. Debido a que Messages API se encuentra actualmente en Beta, necesitamos la versión Beta de nuestro SDK.

Para configurar nuestro servidor, vamos a necesitar cierta información del Panel para desarrolladores de Vonage. Primero, crearemos una nueva aplicación de Vonage. Dale un nombre bonito como isItDelivering. Y luego haz clic en "Generar clave pública y privada".

Generate Public/Private KeyGenerate Public/Private Key

Esto generará automáticamente una clave para la autenticación, que utilizaremos más adelante. Mueva la clave generada a la raíz de su proyecto local.

En este punto, tu proyecto debería contener tu archivo index, módulos node, package.json, y tu archivo ENV. Si ejecutas el comando lstu proyecto debería tener este aspecto:

Project Should Include index.js, node_modules, pack.json, private.keyProject Should Include index.js, node_modules, pack.json, private.key

Como puedes ver, nuestra aplicación de Vonage nos permite activar/desactivar varias capacidades a través de las diferentes API de Vonage. Querremos activar las capacidades de Mensajes. Ahora se nos pedirán dos URL correspondientes a webhooks que la Messages API utilizará para interactuar con nuestra aplicación bot.

Conectar con el mundo exterior

Configurar ngrok

Hay varias formas de hacer que nuestro servidor de desarrollo local sea accesible externamente, pero una de las formas más sencillas es con ngrok. Puedes leer este artículo para una explicación más detallada de cómo funciona ngrok.

Para nuestros propósitos, sólo necesitamos ponerlo en marcha y copiar la URL que nos proporciona.

Después de tener ngrok instalado en tu máquina, necesitaremos iniciarlo. Para iniciar ngrok, abra una nueva ventana de terminal y ejecute lo siguiente desde la línea de comandos:

$ ngrok http 3000

Ahora verá una interfaz de registro ngrok en su ventana de terminal. Cerca de la parte superior de la interfaz hay una línea que empieza por Forwarding y contiene dos URLs. La primera es la URL de ngrok accesible externamente, que termina con ngrok.io seguido de http://localhost:3000que es tu servidor de desarrollo local. Ahora, cuando tú o Vonage se comuniquen con la ngrok.io URL, la reenviará a tu servidor local.

Ahora en nuestro panel de Vonage agregaremos nuestras URL de ngrok y agregaremos las rutas URL apropiadas. Una vez que tus URL tengan este aspecto, puedes presionar el botón "Generar nueva aplicación".

Webook URLsWebhook URLs

Conéctate con Vonage

Conecta tu cuenta de Vonage

En el archivo ENV de tu proyecto, tendrás que añadir 3 variables de entorno; API_KEY , API_SECRETy APP_ID.

Puede encontrar su API_KEY y API_SECRET en la página de inicio de tu panel de Vonage:

Dashboard ENV VariablesDashboard ENV Variables

Su APP_ID se encuentra en la página de configuración de la aplicación que ha generado. Encontrará su aplicación en Your Applications en la barra de navegación de la izquierda. Su APP_ID tendrá este aspecto:

APP_ID in DashboardAPP_ID in Dashboard

Una vez que los hayas copiado y pegado en tu proyecto, tu archivo ENV debería tener este aspecto:

API_KEY="XXXXXXXXX"
API_SECRET="XXXXXXXXX"
APP_ID="XXXXXXXXX"

Primeros pasos con Messages API Sandbox

Añadir usuarios a su Sandbox

Utilizaremos el Vonage Facebook Sandbox. Puedes encontrar el Sandbox en tu panel de control de Vonage en la pestaña Mensajes y envío en el lado izquierdo, o haz clic aquí. Una vez que hagas clic Add to Sandbox en la pestaña Facebook Messenger, tu pantalla debería verse así:

Set up your sandboxSet up your sandbox

El Sandbox de Messages API permite probar aplicaciones rápidamente sin tener que esperar a la aprobación de la Business Account. El Sandbox utiliza un enfoque de lista blanca para permitir usuarios de prueba. Puede invitar a otros usuarios a la lista blanca a través del botón Send invite email button o enviándoles el hipervínculo click this link. El enlace abrirá una sesión de Facebook Messenger. A continuación, el usuario tendrá que enviar la frase de contraseña para ser añadido a la lista blanca. Para más información aquí.

Conexión de la aplicación al entorno de pruebas

Ahora tendremos que decirle a nuestro Sandbox que escuche las peticiones de nuestra Aplicación y las entregue a Facebook Messenger. Esto se hace a través de nuestras URLs ngrok. Necesitaremos añadir las mismas URLs ngrok de antes, así:

Messages API Sandbox ngrok URLSMessages API Sandbox ngrok URLS

Una vez que pulsemos el botón Save webhooks habremos completado nuestra configuración y podremos empezar a programar.

Todo el código posterior irá en nuestro archivo index.js archivo.

Configuración de un servidor Express

Construir un servidor Boilerplate con dependencias

En primer lugar, vamos a configurar un servidor Express en nuestro archivo index.js que importará las librerías necesarias y se ejecutará en el puerto 3000:

// access our environment variables
require('dotenv').config();
// access the Vonage SDK so we can use the Voange object and API
const Vonage = require('@vonage/server-sdk');
// access Got library which allows us to make HTTP request to WOLT API
const got = require('got');

// boilerplate Express setup
const express = require('express');
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: false }));

app.listen(3000);

Enviar un mensaje básico de Facebook

Enviar un mensaje de Facebook desde nuestra aplicación

Necesitamos inicializar una instancia de Vonage, pasándole nuestras variables ENV, y luego decirle que use Vonage Sandbox como el host utilizado para realizar las solicitudes HTTP. Podemos copiar el siguiente código en nuestro

// initialize a new Vonage instance, with ENV variables/keys
const vonage = new Vonage(
  {
    apiKey: process.env.API_KEY,
    apiSecret: process.env.API_SECRET,
    applicationId: process.env.APP_ID,
    privateKey: './private.key'
  },
  {
    apiHost: 'https://messages-sandbox.nexmo.com/',
  }
 );

A continuación, utilizamos ese objeto de Vonage para enviar una solicitud POST en nuestra /inbound y necesitamos proveer los parámetros mínimos: type y text.

// Basic Sandbox Messaging
app.post('/inbound', (req, res) => {
  vonage.channel.send(
    req.body.from,
    req.body.to,
    {
      content: {
        type: 'text',
        text: 'You must be hungry! 🍕'
      },
    },
    (err, data) => {
      if(err){
        console.log(err);
      } else{
          console.log(data.message_uuid);
      }
    }
  );
  res.send('ok');
});

app.post('/status', (req, res) => {
  res.send('ok');
});

Así que ahora en una segunda ventana de terminal, separada de nuestro servidor ngrok, tenemos que encender nuestro servidor Express:

$ node index.js

¡Y podemos interactuar con nuestro Bot de Facebook!

Basic Facebook SandBox MessageBasic Facebook SandBox Message

Recibir información sobre restaurantes de la API Wolt

Realización de la solicitud HTTP

Utilizando el https://restaurant-api.wolt.com/v3/venues/slug/{restaurant} endpoint, sabemos que podemos recibir todo tipo de información sobre el restaurante. El JSON devuelto tiene este aspecto:

Wolt Returned JSONWolt Returned JSON

Podemos ver que dentro del índice cero, hay una propiedad llamada name de tipo array. En el índice cero de name hay un booleano llamado onlineque nos da el estado actual de entrega del restaurante. Así que podemos crear una función que toma el nombre de un restaurante y devuelve el objeto restaurante de Wolt:

// call Wolt API for restaurant info
const getRestaurant = async (reqRestaurant) => {
  const response = await got.get(`https://restaurant-api.wolt.com/v3/venues/slug/${reqRestaurant}`)
      .json();
  return response.results[0];
}

Compruebe si el restaurante recibido está en línea

Utilizando la propiedad online dentro del objeto restaurant queremos crear una lógica que determine qué mensaje enviamos al usuario. Podemos escribir la siguiente función:

const firstStatusCheck = (restaurant, recipient) => {
  if (restaurant.online) {
    sendFacebookMessage(`Hey, ${restaurant.name[0].value} is now accepting orders!!`, recipient);
  } else {
	  sendFacebookMessage(`Sorry, ${restaurant.name[0].value} is currently offline. I'll ping you when it's open again!`, recipient);
    }
}

La función firstStatusCheck ha abstraído nuestro código de Vonage para enviar el mensaje de Facebook en una función llamada sendFacebookMessage. Esta función ahora puede enviar cualquier mensaje desde nuestra cuenta Sandbox siempre y cuando le pasemos dos parámetros text y recipient.

Podemos utilizar una variable constante SENDER para pasar la información del id del remitente de la cuenta Sandbox. Primero, la declararemos.

let SENDER;

Y luego lo asignamos cuando recibimos el req del /inbound punto final:

app.post('/inbound', async(req, res) => {
  SENDER = req.body.from;

Ahora el sendFacebookMessage debería tener este aspecto:

const sendFacebookMessage = async (text, recipient) => {
  vonage.channel.send(
    SENDER,
    recipient,
    {
      content: {
        type: 'text',
        text: text,
      },
    },
    (err, data) => {
      if (err) {
        console.log(err);
      } else {
        console.log(data.message_uuid);
      }
     }
   );
 }

Enviar un mensaje al usuario en función del estado del restaurante

Combinando nuestra nueva funcionalidad, podemos actualizar nuestra sencilla mensajería Sandbox para indicar al usuario si el restaurante solicitado está actualmente en línea o no.

// Enhanced Sandbox Messaging
app.post('/inbound', async(req, res) => {
	SENDER = req.body.from;
	const recipient = await req.body.to;
	const requestedRestaurant = await req.body.message.content.text.split('/').pop();
	const restaurant = await getRestaurant(requestedRestaurant);
	firstStatusCheck(restaurant, recipient);
	res.send('ok');
});

Bucle si el restaurante está fuera de servicio

Ahora que hemos creado la lógica basada en el estado del restaurante, queremos seguir comprobando ese estado hasta que el restaurante finalmente vuelva a estar en línea. Así que tenemos que construir los tres últimos pasos de nuestro pseudocódigo:

  1. Si el restaurante no está en línea, añádalo a una lista de restaurantes sin conexión

  2. Comprueba continuamente la lista de restaurantes sin conexión para ver si hay algún cambio de estado

  3. Si un restaurante se conecta, envía un mensaje al usuario y lo elimina de la lista de restaurantes desconectados.

Creación de una base de datos en memoria de restaurantes fuera de línea

En este punto utilizaremos nuestra LokiJS biblioteca. LokiJS es una base de datos en memoria que nos permitirá hacer un seguimiento de cada restaurante solicitado de una forma sencilla y en tiempo de ejecución. Si has usado MongoDB, LokiJS te resultará muy familiar.

En primer lugar, tenemos que incluir Loki con nuestras otras dependencias:

const loki = require('lokijs');

A continuación, tendremos que instanciar nuestra base de datos:

let db = new loki("restaurants.db");
let restaurants = db.addCollection("restaurants");

Cada entrada de restaurante contendrá 4 datos: nombre, estado en línea, destinatario y slug. Name es el nombre del restaurante. Online status es un booleano que indica si el restaurante está actualmente en línea. Recipient es la información de usuario de la Messages API, que nos permitirá saber a quién hay que notificar. Y por último, slug es la terminación de la URL que la API Wolt utiliza para encontrar un restaurante.

Ahora que tenemos una base de datos, ¡podemos empezar a añadirle nuestros restaurantes offline! Podemos utilizar la siguiente función para añadir restaurantes a nuestra lista offline:

 const addRestaurantToDb = (restaurant, recipient) => {
  restaurants.insert({name: restaurant.name[0].value, online: restaurant.online, recipient: recipient, slug: restaurant.slug});
 }

Ahora tendremos que actualizar nuestro firstStatusCheck para añadir restaurantes a la lista offline.

// Check initially whether restaurant is online or should it be added to list of offline restaurants to check
const firstStatusCheck = (restaurant, recipient) => {
  if (restaurant.online) {
    sendFacebookMessage(`Hey, ${restaurant.name[0].value} is now accepting orders!!`, recipient);
  } else {
      sendFacebookMessage(`Sorry, ${restaurant.name[0].value} is currently offline. I'll ping you when it's open again!`, recipient);
      addRestaurantToDb(restaurant, recipient);
    }
}

Compruebe continuamente los cambios de estado

Ahora que tenemos una lista de restaurantes offline, queremos comprobar si vuelven a estar online. Como queremos hacerlo de forma regular y continua, utilizaremos la función integrada setInterval incorporada:

setInterval(function(){offlineRestaurantLookup(req)} , INTERVAL);

La constante INTERVAL indica a setInterval la frecuencia con la que debe ejecutar la función offlineRestaurantLookup función. Definimos esto en la parte superior del archivo junto a SENDER. Por defecto, vamos a comprobar cada 60 segundos:

const INTERVAL = 60000;

El sitio offlineRestaurantLookup recuperará todos los restaurantes de la base de datos de restaurantes sin conexión y, a continuación, comprobará que cada restaurante sigue sin conexión.

const offlineRestaurantLookup = async () => {
  let offlineRestaurants = restaurants.data;
  offlineRestaurants.forEach(await checkIsStill0ffline);
}

La función checkIsStill0ffline comprueba si un restaurante está en línea. Si el restaurante está en línea, enviará un mensaje al usuario correcto y lo eliminará de la lista de restaurantes sin conexión.

const checkIsStill0ffline = async (restaurant) => {
  const checkedRestaurant = await getRestaurant(restaurant.slug);
  if (checkedRestaurant.online) {
    sendFacebookMessage(`Hey, ${restaurant.name} is now accepting orders!!`, restaurant.recipient);
    restaurants.chain().find({'name': restaurant.name}).remove();
  }
}

Ahora podemos añadir la funcionalidad setInterval funcionalidad debajo de nuestra lógica de Sandbox Messaging:

// Enhanced Sandbox Messaging
app.post('/inbound', async(req, res) => {
  SENDER = req.body.from;
  const recipient = await req.body.to;
  const requestedRestaurant = await req.body.message.content.text.split('/').pop();
  const restaurant = await getRestaurant(requestedRestaurant);
  firstStatusCheck(restaurant, recipient);
  res.send('ok');
});

app.post('/status', (req, res) => {
  res.send('ok');
});

setInterval(function(){offlineRestaurantLookup()} , INTERVAL);

app.listen(3000);

Y ahora podemos ejecutar el programa, viendo que cuando un restaurante está fuera de línea recibimos un mensaje notificándolo, y cuando los restaurantes entran en línea se nos actualiza el nuevo estado. Sugiero probar la aplicación por la mañana y ver cómo los restaurantes abren de repente para comer. Es divertido recibir las notificaciones push de Facebook Messenger en el teléfono.

Enhanced Facebook SandBox MessageEnhanced Facebook SandBox Message

El futuro

  • En este tutorial, hemos utilizado la funcionalidad de Facebook Messenger de la Messages API, pero podríamos ampliar esta aplicación para proporcionar capacidades omnichannel con WhatsApp y SMS. Imagina un caso de uso muy urgente (tengo en mente una tienda de bagels en particular los sábados por la mañana) en el que quisieras ser notificado inmediatamente sobre un cambio de estado; las alertas omnichannel serían útiles.

  • Podríamos ampliar este código para que las alertas sean más inteligentes en función de los horarios de entrega, la proximidad del usuario a los restaurantes, etc. También podríamos mantener múltiples trabajos.

  • Podríamos sacar la aplicación del Sandbox y conectarla a una Account de Facebook de empresa.

El código final del tutorial se puede en GitHub. Me encantaría saber qué has creado con la API de Messages API de Vonage. Únete a la conversación en nuestra Slack de la comunidad ¡y comparte tu historia!

Compartir:

https://a.storyblok.com/f/270183/384x384/e4e7d1452e/benjamin-aronov.png
Benjamin AronovDefensor del Desarrollador

Benjamin Aronov es desarrollador de Vonage. Es un constructor de comunidades con experiencia en Ruby on Rails. Benjamin disfruta de las playas de Tel Aviv, a la que llama hogar. Su base en Tel Aviv le permite conocer y aprender de algunos de los mejores fundadores de startups del mundo. Fuera de la tecnología, a Benjamin le encanta viajar por el mundo en busca del perfecto pain au chocolat.