`

Trackeando COVID-19 con NodeJS y Telegram Bot

Buenas!

Ya que todos vamos a tener que estar en casa y cumplir la cuarentena se me ocurrió usar los datos que ya tenemos del COVID-19 y hacer una alerta en Telegram que nos avise cuando cambia el numero de casos, tanto para bien como para mal, en nuestros países y en el mundo.

Qué vamos a aprender?

  • Data Fetch
  • Cron Schedule
  • Telegram Bot

Resultado final

Antes de empezar y como ya saben, todo el contenido es 100% gratuito, con lo único que me ayudarían es dándome un follow en Twitter: @DamianCatanzaro  y compartiendo el contenido para que más gente lo pueda usar 😁. Si les gusta mucho el contenido pueden apoyarme con un Cafecito ☕️!

Para arrancar vamos a iniciar nuestro proyecto de NodeJS creando una carpeta, entrando desde la terminal y haciendo un setup con npm.

npm init

Y vamos a pasar a instalar los paquetes que necesitamos.

Para el fetch de la data vamos a usar axios, para comunicarnos con el bot de Telegram vamos a usar Telegraf y para crear un proceso que verifique cada x cantidad de tiempo si la data fue actualizada node-schedule.

Así que pasemos a instalarlos.

npm install axios telegraf node-schedule

Y antes de empezar a codear vamos a crear nuestro bot en Telegram, para hacer esto vamos a tener que hablarle a @BotFather en Telegram por supuesto y decirle lo siguiente:

/newbot

Nos va a preguntar el nombre del bot, tiene que ser todo en minúsculas y nos va a dar un access token que no tenemos que compartir con nadie, es privado del bot.

Además de ese access token necesitamos el ChatID, que es el ChatID? Es un identificador que nos crea telegram cuando iniciamos un chat con nuestro bot, esto le va a permitir al bot saber a donde enviarnos el mensaje de alerta, para esto vamos a hablarle al bot por telegram y escribir

/start

Una vez que tiremos ese comando, vamos al navegador y entramos a la siguiente URL:

https://api.telegram.org/bot<ACCESS_TOKEN>/getUpdates

Y tienen que remplazar donde dice "<ACCESS_TOKEN>" por el token que les proporcionó Telegram anteriormente, una vez que entran van a tener que buscar la parte que dice chat id y la guardan.

Ese que está seleccionado en azul es el que necesitamos.

Voy a crear todo adentro de un index.js y voy a ir explicando por partes como funciona.

const axios = require("axios"),
    schedule = require("node-schedule"),
    Telegraf = require("telegraf");

const ACCESS_TOKEN_TELEGRAM = "";
const CHAT_ID = "";

class Tracker {
    constructor(telegramBot) {
        this.telegramBot = telegramBot;

        this.country = {
            confirmed: 0,
            deaths: 0,
            recovered: 0
        };

        this.global = {
            confirmed: 0,
            deaths: 0,
            recovered: 0
        };
    }

    initialize = async () => {
        this.setDataAndNotify();

        schedule.scheduleJob("0 */1 * * *", () => {
            this.setDataAndNotify();
        });
    };

    getDataFromCountry = async country => {
        try {
            const result = await axios.get(
                `https://wuhan-coronavirus-api.laeyoung.endpoint.ainize.ai/jhu-edu/latest?iso2=${country}&onlyCountries=true`
            );

            return result.data;
        } catch (err) {
            throw new Error(`Error in fetch country ${country}`);
        }
    };

    getDataGlobal = async () => {
        try {
            const result = await axios.get(
                `https://wuhan-coronavirus-api.laeyoung.endpoint.ainize.ai/jhu-edu/brief`
            );

            return result.data;
        } catch (err) {
            throw new Error("Error in fetch global");
        }
    };

    setDataAndNotify = async () => {
        const country = await this.getDataFromCountry("AR");
        const global = await this.getDataGlobal();

        if (
            this.country.confirmed != country[0].confirmed ||
            this.country.deaths != country[0].deaths ||
            this.country.recovered != country[0].recovered
        ) {
            this.country = {
                confirmed: country[0].confirmed,
                deaths: country[0].deaths,
                recovered: country[0].recovered
            };

            this.telegramBot.telegram.sendMessage(
                CHAT_ID,
                `🦠 | Country | Confirmed: ${this.country.confirmed}, Deaths: ${this.country.deaths}, Recovered: ${this.country.recovered}`
            );
        }

        if (
            this.global.confirmed != global.confirmed ||
            this.global.deaths != global.deaths ||
            this.global.recovered != global.recovered
        ) {
            this.global = {
                confirmed: global.confirmed,
                deaths: global.deaths,
                recovered: global.recovered
            };

            this.telegramBot.telegram.sendMessage(
                CHAT_ID,
                `🦠 | Global | Confirmed: ${this.global.confirmed}, Deaths: ${this.global.deaths}, Recovered: ${this.global.recovered}`
            );
        }
    };
}

const telegramBot = new Telegraf(ACCESS_TOKEN_TELEGRAM);

const tracker = new Tracker(telegramBot);
tracker.initialize();

Este es nuestro código, que nos va a informar cada vez que se actualice data de nuestro país o del mundo. Pasemos a explicarlo.

Y para ejecutarlo en la misma terminal donde instalamos nuestras dependencias con npm escribimos:

node index.js

Como primera medida agregamos todos los módulos que incluimos y también agregamos tanto el token como chatid de Telegram.

const axios = require("axios"),
    schedule = require("node-schedule"),
    Telegraf = require("telegraf");

const ACCESS_TOKEN_TELEGRAM = "";
const CHAT_ID = "";

Dentro de la clase tenemos 2 métodos que nos van a traer tanto la data del país que le pasemos (yo en este caso le pasé Argentina "AR") como el global. Con axios podemos hacer una llamada a esa API que nos va a proporcionar toda la información actual del COVID-19 a nivel mundial, les dejo el GitHub por si quieren ver un poco más.

getDataFromCountry = async country => {
    try {
        const result = await axios.get(
            `https://wuhan-coronavirus-api.laeyoung.endpoint.ainize.ai/jhu-edu/latest?iso2=${country}&onlyCountries=true`
        );

        return result.data;
    } catch (err) {
        throw new Error(`Error in fetch country ${country}`);
    }
};

getDataGlobal = async () => {
    try {
        const result = await axios.get(
            `https://wuhan-coronavirus-api.laeyoung.endpoint.ainize.ai/jhu-edu/brief`
        );

        return result.data;
    } catch (err) {
        throw new Error("Error in fetch global");
    }
};

Y como ante-último tenemos el metodo que va a fijarse si los atributos de casos confirmados, muertes y recuperaciones son iguales al anterior, los va a asignar y nos va a avisar por Telegram si hubo alguna actualización.

setDataAndNotify = async () => {
    const country = await this.getDataFromCountry("AR");
    const global = await this.getDataGlobal();

    if (
        this.country.confirmed != country[0].confirmed ||
        this.country.deaths != country[0].deaths ||
        this.country.recovered != country[0].recovered
    ) {
        this.country = {
            confirmed: country[0].confirmed,
            deaths: country[0].deaths,
            recovered: country[0].recovered
        };

        this.telegramBot.telegram.sendMessage(
            CHAT_ID,
            `🦠 | Country | Confirmed: ${this.country.confirmed}, Deaths: ${this.country.deaths}, Recovered: ${this.country.recovered}`
        );
    }

    if (
        this.global.confirmed != global.confirmed ||
        this.global.deaths != global.deaths ||
        this.global.recovered != global.recovered
    ) {
        this.global = {
            confirmed: global.confirmed,
            deaths: global.deaths,
            recovered: global.recovered
        };

        this.telegramBot.telegram.sendMessage(
            CHAT_ID,
            `🦠 | Global | Confirmed: ${this.global.confirmed}, Deaths: ${this.global.deaths}, Recovered: ${this.global.recovered}`
        );
    }
};

Como ultimo, necesitamos un proceso que se ejecute cada X cantidad de tiempo, que recorra la API en busca de nuevos casos y que nos informe de ser necesario, para esto vamos a usar la librería node-schedule, esta librería usa el formato de expresiones de cron, les va a parecer un poco raro al principio pero es fácil acostumbrarse y les voy a dejar material abajo para que lo entiendan mejor.

initialize = async () => {
    this.setDataAndNotify();

    schedule.scheduleJob("0 */1 * * *", () => {
        this.setDataAndNotify();
    });
};

Esa expresión "0 */1 * * *" significa ejecutate cada una hora. Hay una herramienta muy buena que se llama crontab.guru que nos explica como armar estas expresiones de una manera mucho más sencilla y gráfica.

Cada vez que se vayan actualizando los casos para bien o para mal nuestro bot nos va a avisar con un mensajito en Telegram.

Como siempre, voy a dejar un challenge por si tienen ganas de seguir desafiándose y agregando valor a lo que aprenden.

Challenge 1: Ordenar números con separadores de miles y mostrar porcentajes de muertos como recuperados.

Challenge 2: Al fin del día mostrar porcentajes de avance con respecto al día anterior.

Y esto es todo!

Estamos pasando un momento realmente difícil, todo sabemos que quedarnos en cuarentena no es fácil pero tenemos que hacer el esfuerzo para que perjudicar a los demás.

Si pueden resolver los challenges no duden en compartirlos, los voy a estar leyendo en mi Twitter @DamianCatanzaro

También se pueden suscribir al blog o seguirme en Twitter para recibir todas las notificaciones de cuando suba nuevo contenido!

Nos vemos! Stay safe.

Follow @DamianCatanzaro