https://a.storyblok.com/f/270183/34797/0a686dc8fb/blog_air-quality-reporting_1200x600.png

Crie um serviço de relatórios sobre a qualidade do ar com a Messages API

Publicado em December 2, 2020

Tempo de leitura: 12 minutos

Você já pensou em ampliar sua aplicação atual para interagir com vários canais de comunicação? E se pudéssemos usar essa ideia para chamar a atenção para questões como a poluição do ar e as mudanças climáticas?

O projeto World Air Quality Index é uma iniciativa sem fins lucrativos lançada em 2007. Sua missão é promover a conscientização sobre a poluição do ar e garantir o acesso a informações sobre a qualidade do ar em todo o mundo. O projeto disponibiliza APIs REST para acessar dados de estações de monitoramento meteorológico e de qualidade do ar em todo o mundo. Você também pode usar outras fontes de dados para criar um serviço voltado para questões sociais!

Neste exemplo, vamos criar um serviço baseado no Node.js — um ambiente de execução de JavaScript — e na Messages API da Vonage—, que enviará informações sobre a qualidade do ar atual em um determinado local pelo WhatsApp e pelo Facebook Messenger.

O código-fonte do exemplo que vamos criar também pode ser encontrado em GitHub.

Configurar o ambiente de desenvolvimento

Precisaremos abrir um ngrok túnel para a nossa aplicação a fim de disponibilizá-la na Internet com o mínimo de configuração. Após a instalação ngrok, abra um terminal e execute ngrok http 3070 para expor sua porta local 3070 à Internet. Certifique-se de substituir esse valor usando a PORT variável em .env. Copie a URL HTTPS exibida pelo ngrok no console e anote-a.

Screenshot of ngrok running in a terminal emulator

Agora é hora de instalar as dependências necessárias para o aplicativo. Execute npm init -y para criar um package.json arquivo. Usaremos o Express.js — um framework popular de aplicativos web para Node.js — e o Axios — uma biblioteca de cliente HTTP — para este projeto, além do Dotenv — um módulo para gerenciar variáveis de ambiente. Mais tarde, também utilizaremos o Dedent e o Commander.js para implementar mais alguns recursos. Instale esses módulos executando:

npm install --save express axios dotenv dedent commander

Como faremos alterações em nosso código-fonte de tempos em tempos, podemos economizar algumas teclas digitando instalando o Nodemon, que monitora continuamente as alterações e reinicia o aplicativo automaticamente. Instale-o como uma dependência de desenvolvimento executando

npm install -D nodemon

Para este tutorial, nosso ponto de partida será um arquivo chamado lib/index.js. Adicione ou atualize o main e as script chaves em package.json para executar o aplicativo usando o nodemon:

// package.json

{
  ...
  "main": "lib/index.js",
  ...
  "scripts": {
    "start": "node .",
    "dev": "nodemon .",
  },
  ...
}

Copie o conteúdo de .env.example no diretório principal para um novo arquivo chamado .env. Depois de fazer login no Painel da API da Vonage, localize sua Chave de API e seu Segredo de API e atualize os valores em .env. Há também algumas variáveis adicionais que serão atribuídas nas próximas seções.

Receber uma mensagem recebida usando a Messages API

Sempre que a Vonage recebe uma mensagem no seu número de telefone virtual ou por meio de um dos outros canais, os servidores da Vonage enviam uma solicitação HTTP para um endpoint de webhook definido, com uma carga JSON. Para este tutorial, definimos que a /webhook/inbound rota em nosso aplicativo ficará à espera de todas essas solicitações.

Para garantir que recebamos essa solicitação, precisamos configurar o Ambiente Sandbox, que você encontra no Painel de Controle da API da Vonage, na seção “Mensagens e Envio”. Defina o Webhook de Mensagens de Entrada (HTTP POST) como <ngrok-https-url>/webhook/inbound e clique em “Salvar webhooks”.

Screenshot showing setting webhook

Na mesma página, vincule um account de teste para enviar mensagens. Clique nos links “Adicionar ao Sandbox” nos canais do WhatsApp e do Messenger. Em seguida, escaneie o código QR no seu celular ou clique no link fornecido. Geralmente, isso envolve o envio de uma senha para um número ou página provisionada para o sandbox. Depois de vincular sua conta de teste e definir o endpoint do webhook, você pode prosseguir. Salve o número de telefone do sandbox mencionado no painel de controle na sua agenda de contatos para facilitar o acesso.

Vamos criar uma aplicação em Express.js para escutar na porta 3070 pelas solicitações de webhook. Os requisitos mínimos são aceitar solicitações HTTP POST nessa rota e enviar um código de status de 200. Em nosso aplicativo Express.js, essa carga útil pode ser acessada por meio do req.body objeto. Para dar uma olhada nos dados da carga útil da solicitação, execute a aplicação digitando npm run dev.

// lib/index.js

require("dotenv").config();

const express = require("express");

const app = express();
const PORT = process.env.PORT || 3070;

app.use(express.json());
app.use(express.urlencoded({ extended: true }));

app.post("/webhook/inbound", (req, res) => {
  console.log(req.body);
  res.status(200).end();
});

app.listen(PORT, () => console.log(`Listening on Port ${PORT}...`));

Tente enviar uma mensagem pelo WhatsApp para o número da sandbox e observe o resultado na janela do terminal em que seu aplicativo está sendo executado. Envie outra mensagem pelo aplicativo Messenger para a página da sandbox e observe o resultado novamente.

Screenshot showing request body for Webhook requests for different channels

Os resultados apresentados no exemplo acima mostram que os diferentes canais podem ser diferenciados por meio da validação req.body.from.type. Dependendo do canal, observamos também que as mensagens recebidas podem ser provenientes tanto de um número de telefone quanto de um ID de página/conta. A mensagem enviada pode ser acessada por meio do req.body.message objeto no corpo da solicitação.

Defina o valor de VONAGE_NUMBER para o número de telefone recebido em req.body.to.number e VONAGE_PAGE_ID para o ID da página, conforme em req.body.to.id nas respectivas variáveis em .env , já que estamos usando a sandbox. Na prática, isso seria substituído por um número de Account do WhatsApp Business e um ID de página do Facebook vinculados a um aplicativo da Vonage.

Enviar uma mensagem usando a Messages API

Ao usar a Messages API, o envio de uma mensagem para um canal envolve o envio de uma solicitação HTTP POST com um objeto de mensagem para o endpoint da API. Ao usar o ambiente de teste, o endpoint é: https://messages-sandbox.nexmo.com/v0.1/messages.

A Referência da Messages APImostra que a solicitação deve conter um Authorization cabeçalho com o valor Basic base64(apiKey):base64(apiToken) ou Bearer jwtToken e um objeto de mensagem no corpo da solicitação. Para usar isso, atualize seu /lib/utils.js arquivo com o exemplo abaixo:

// lib/utils.js

require("dotenv").config();

const Axios = require("axios");

const sendMessage = async (message, body) => {
  await Axios.post(
    "https://messages-sandbox.nexmo.com/v0.1/messages",
    {
      from: {
        type: body.from.type,
        number: process.env.VONAGE_NUMBER
      },
      to: {
        type: body.from.type,
        number: body.from.number
      },
      message: {
        content: {
          type: "text",
          text: message
        }
      }
    },
    {
      auth: {
        username: process.env.VONAGE_API_KEY,
        password: process.env.VONAGE_API_SECRET
      }
    }
  );
};

module.exports = {
  sendMessage,
};

A função auxiliar sendMessage receberá o corpo da mensagem a ser enviada para o número do WhatsApp definido. O objeto de mensagem pode ser construído dinamicamente para oferecer suporte a vários canais; você pode implementar isso em outra função utilitária.

Atualize seu /lib/index.js arquivo; dentro da função do webhook, chame a sendMessage função com a mensagem que você deseja enviar, conforme mostrado abaixo:

// lib/index.js
...
const { sendMessage } = require("./utils");

app.post("/webhook/inbound", (req, res) => {
  sendMessage("Thanks for sending a message!", req.body);
  res.status(200).end();
});
...

Criamos uma estrutura básica para um serviço de conversação que utilizará a Messages API para enviar e receber mensagens pelo WhatsApp e pelo Messenger. Experimente enviar uma mensagem para o número de teste da Vonage no WhatsApp!

Obter dados das APIs do Índice Mundial de Qualidade do Ar

O Projeto Índice Mundial de Qualidade do Ar oferece APIs JSON para dados de qualidade do ar quase em tempo real. Para ter acesso aos dados, inscreva-se para obter um token de API. Receberemos um link de verificação no endereço de e-mail fornecido nesta página, que nos redirecionará para uma página exibindo o token da API. Defina o valor de AQICN_TOKEN em .env para o token exibido nessa página.

Pesquise uma estação de monitoramento da qualidade do ar correspondente a uma determinada cidade usando a API de pesquisa WAQI. A solicitação HTTP GET para https://api.waqi.info/search/ tem dois parâmetros de consulta obrigatórios — keyword usado como termo de pesquisa para encontrar o nome de uma estação ou cidade e token que se refere ao token da API WAQI.

Ao enviar a solicitação pelo Postman ou pelo Insomnia — ambos aplicativos populares com interface gráfica para depuração de solicitações de API HTTP —, podemos observar que a resposta para a palavra-chave london contém metadados limitados da estação para cada resultado da pesquisa.

// GET https://api.waqi.info/search/?token={{AQICN_TOKEN}}&keyword=london
{
  "status": "ok",
  "data": [
    {
      "uid": 5724,
      "aqi": "36",
      "time": {
        "tz": "+01:00",
        "stime": "2020-11-04 05:00:00",
        "vtime": 1604462400
      },
      "station": {
        "name": "London",
        "geo": [
          51.5073509,
          -0.1277583
        ],
        "url": "london"
      }
    },
    ...
  ]
}

É hora de implementar uma função utilitária para que nosso aplicativo obtenha o primeiro resultado da pesquisa e a utilize para recuperar os dados esperados.

// lib/utils.js
...
const getStation = async (keyword) => {
  const stationData = await Axios.get(
    "https://api.waqi.info/search/",
    {
    params: {
      token: process.env.AQICN_TOKEN,
      keyword
    }
  });
  if (stationData.data.data.length === 0) {
    return { error: "No Stations Found. Try Again." };
  }
  return stationData.data.data[0].station;
};
...

Para obter os dados do feed da estação, faça outra solicitação HTTP GET, desta vez para a API WAQI City/Station Feed. O endpoint dessa API é https://api.waqi.info/feed/<station-url>/ onde station-url corresponde ao valor da url chave no station objeto retornado por getStation. O token da API também é necessário como parâmetro de consulta.

A solicitação para a estação retornou london, é retornado um objeto JSON que contém medições brutas e metadados detalhados da estação, conforme mostrado abaixo:

// GET https://api.waqi.info/feed/london/?token={{AQICN_TOKEN}}
{
  "status": "ok",
  "data": {
    "aqi": 36,
    "idx": 5724,
    "attributions": [
      {
        "url": "http://uk-air.defra.gov.uk/",
        "name": "UK-AIR, air quality information resource - Defra, UK",
        "logo": "UK-Department-for-environment-food-and-rural-affairs.png"
      },
      {
        "url": "https://londonair.org.uk/",
        "name": "London Air Quality Network - Environmental Research Group, King's College London",
        "logo": "UK-London-Kings-College.png"
      },
      {
        "url": "https://waqi.info/",
        "name": "World Air Quality Index Project"
      }
    ],
    "city": {
      "geo": [51.5073509, -0.1277583],
      "name": "London",
      "url": "https://aqicn.org/city/london"
    },
    "dominentpol": "pm25",
    "iaqi": {
      "co": { "v": 7.4 },
      "h": { "v": 92 },
      "no2": { "v": 23.3 },
      "o3": { "v": 2.9 },
      "p": { "v": 1029.4 },
      "pm10": { "v": 16 },
      "pm25": { "v": 36 },
      "so2": { "v": 3.4 },
      "t": { "v": 3.8 },
      "w": { "v": 3.7 }
    },
    "time": {
      "s": "2020-11-04 05:00:00",
      "tz": "+00:00",
      "v": 1604466000,
      "iso": "2020-11-04T05:00:00Z"
    },
    "forecast": {},
    "debug": { "sync": "2020-11-04T14:41:04+09:00" }
  }
}

Implemente outra função utilitária para fazer essa solicitação. Essa função recebe o station objeto como parâmetro, que é recuperado de getStatione consulta a API para obter os dados da estação. Atualize lib/utils.js adicionando a seguinte getStationData função:

// lib/utils.js
...
const getStationData = async (station) => {
  const aqiData = await Axios.get(
    `https://api.waqi.info/feed/${station.url}/`,
    {
      params: {
        token: process.env.AQICN_TOKEN
      }
    }
  );
  if (aqiData.data.data.status === "error") {
    return { error: "Could not get data. Try Again." };
  }
  return aqiData.data.data;
};
...

Agora podemos usar nossas funções utilitárias para consultar a API WAQI ao receber uma mensagem em um canal compatível com a Messages API do Vonage e enviar uma resposta relevante após processar esses dados.

Responda com informações relevantes

Os dados que obtemos das APIs do WAQI precisam ser processados e tornados “legíveis”. Podemos usar dois modelos diferentes para apresentar os dados — um para um relatório resumido contendo o Índice de Qualidade do Ar e as implicações para a saúde, de acordo com a escala da EPA dos EUA de 2016 — e outro para um relatório detalhado que mencione os níveis de poluentes e informações meteorológicas, juntamente com suas respectivas unidades de medida.

AQI Air Pollution Level Health Implications Cautionary Statement (for PM 2.5)
0-50 Good Air quality is considered satisfactory, and air pollution poses little or no risk. None.
51-100 Moderate Air quality is acceptable; however, for some pollutants there may be a moderate health concern for a very small number of people who are unusually sensitive to air pollution. Active children and adults, and people with respiratory disease, such as asthma, should limit prolonged outdoor exertion.
101-150 Unhealthy for Sensitive Groups Members of sensitive groups may experience health effects. The general public is not likely to be affected. Active children and adults, and people with respiratory disease, such as asthma, should limit prolonged outdoor exertion.
151-200 Unhealthy Everyone may begin to experience health effects; members of sensitive groups may experience more serious health effects. Active children and adults, and people with respiratory disease, such as asthma, should avoid prolonged outdoor exertion; everyone else, especially children, should limit prolonged outdoor exertion.
201-300 Very Unhealthy Health warnings of emergency conditions. The entire population is more likely to be affected. Active children and adults, and people with respiratory disease, such as asthma, should avoid all outdoor exertion; everyone else, especially children, should limit outdoor exertion.
300+ Hazardous Health alert: everyone may experience more serious health effects. Everyone should avoid all outdoor exertion.

Fonte: Noções básicas sobre o AQI, AirNow

Também precisamos identificar os nomes dos poluentes e dos diversos indicadores meteorológicos a partir dessas abreviações enigmáticas. Podemos consultar a referência da API do WAQI e implementar funções utilitárias para fazer isso. Também podemos definir funções auxiliares adicionais nas quais podemos usar métodos de interpolação de strings e, opcionalmente, formatar mensagens para o WhatsApp. A implementação dessas funções auxiliares pode ser encontrada no código-fonte no GitHub.

Dedent é um módulo útil ao lidar com literais de modelo JavaScript ES6 com várias linhas. Você poderá observá-lo sendo amplamente utilizado no código-fonte para manter os espaços em branco, visando uma melhor legibilidade.

Analisar mensagens recebidas com o Commander.js

É útil analisar as mensagens destinadas explicitamente ao serviço e executar ações diferentes para comandos diferentes. O biblioteca Commander.js , inicialmente desenvolvida para aplicativos de linha de comando, pode ser usada para analisar a mensagem recebida em busca de comandos e argumentos.

// lib/index.js
...
const { Command } = require("commander");

const trigger = new Command("vonage-aqi");

// override default cli behaviour
trigger.exitOverride();
trigger.addHelpCommand(false);

trigger
  .command("aqi <searchterm...>")
  .alias("a")
  .action(async (searchterm) => {
    searchterm = searchterm.join(" ");
    // fetch and send the brief report
  });

trigger
  .command("info <searchterm...>")
  .alias("i")
  .action(async (searchterm) => {
    searchterm = searchterm.join(" ");
    // fetch and send the detailed report
  });

trigger
  .command("act")
  .action(async () => {
   // send links to resources and information
});

trigger
  .command("help")
  .alias("h")
  .action(async () => {
    // send help and usage information
  });

...

app.post("/webhook/inbound", async (req, res) => {
  try {
    // pass the incoming message text to Commander.js
    trigger.parse(
      req.body.message.content.text
        .trim().toLowerCase().split(" "),
      {
        from: "user"
      }
    );
  } catch (err) {
    // send message based on the type of error
  } finally {
    res.status(200).end();
  }
});
...

A biblioteca Commander.js oferece suporte a argumentos obrigatórios e opcionais, argumentos variáveis e aliases de comandos, o que torna a tarefa muito mais fácil do que verificar manualmente os comandos e argumentos.

Garanta a entrega com o webhook de status

Podemos configurar uma nova rota para monitorar os eventos que ocorreram após o envio de uma mensagem em /webhook/status. Certifique-se de adicionar isso ao ngrok túnel e salve-o como “Status Webhook” no Painel da API da Vonage, clicando em “Salvar webhooks”.

// lib/index.js
...
app.post("/webhook/status", (req, res) => {
  console.log(req.body);
  res.status(200).end();
});
...

Na próxima vez que nosso serviço receber uma mensagem e responder a ela, observamos diferentes estados da mensagem que foi enviada. O req.body.status campo conterá o status para o qual o objeto da mensagem transitou quando a solicitação do webhook foi enviada. Quando a mensagem é recebida pelos servidores da Vonage, o objeto está no submitted estado. Se a entrega tiver sido realmente bem-sucedida, devemos receber um valor de status que provavelmente será delivered seguido por read.

Se houvesse um erro, o status poderia ser rejected ou undeliverable e, em teoria, poderíamos tratar esse caso separadamente. Observe que a Vonage faz grande parte do trabalho pesado, tentando novamente em intervalos regulares caso a entrega da mensagem tenha falhado.

WhatsApp e Messenger: Espaço de Brincadeiras

Certifique-se de que o aplicativo esteja em execução e que o ngrok túnel esteja salvo no Messages Sandbox. Pegue seu celular e envie mensagens para as contas do sandbox. Não é ótimo quando tudo funciona mesmo?

WhatsApp

Screenshot showing a conversation with the service on WhatsApp

Messenger

Screenshot showing a conversation with the service on Messenger

Conclusão

Este projeto mostra como as APIs da Vonage são flexíveis na integração com praticamente qualquer aplicativo. Abordamos a comunicação multicanal com o WhatsApp e o Messenger e utilizamos as APIs WAQI para este exemplo. Estou curioso para saber o que você poderá criar depois de ler isso!

Leitura complementar

Você pode encontrar o código apresentado neste tutorial e o código-fonte completo do aplicativo em funcionamento no no repositório do GitHub.

Não deixe de conferir a documentação relevante sobre a Messages API em Vonage API Developer e Referência da API da Vonage. Saiba mais sobre como as comunicações com o WhatsApp e Messenger funcionam no Vonage API Developer.

Caso você não tenha um Account na Vonage, cadastre-se hoje mesmo para ganhar créditos gratuitos e usar as APIs da Vonage no seu próximo projeto! Entre em contato conosco no Twitter ou participe do canal da Comunidade no Slack. Conte para a gente o que você planeja criar com as APIs da Vonage!

Compartilhar:

https://a.storyblok.com/f/270183/250x250/a84175ca37/sudipto-ghosh.png
Sudipto GhoshAutor convidado

Sudipto é estudante de graduação na Universidade de Délhi, com formação em Ciência da Computação e Matemática. Ele sempre demonstra entusiasmo e curiosidade em saber como as coisas funcionam e como são construídas. Possui experiência no desenvolvimento de Applications com JavaScript, TypeScript, Python e Java, e seus interesses de pesquisa incluem computação distribuída e sistemas de recomendação.