Leitura de RSS Feed na prática em nodeJS

Integre RSS Feeds em Seu Bot do Discord com Node.js

Vamos montar uma funcionalidade no nosso BOT do Discord para que ele possa ler um feed de notícias e postar as novidades em um dos nossos canais de texto!

O primeiro passo é ter um feed de notícias para fazer a leitura. Como exemplo, vamos utilizar o feed aqui do nosso blog 4Future:

const feedURL = "https://4future.com.br/index.php/feed/"

Ao colocar esse endereço no seu navegador, cairá numa página com os dados do feed. Vamos montar uma aplicação que fará a leitura desse feed.

Ao abrir o endereço do feed no navegador você não vai entender nada

Instale a biblioteca RSSParser na sua aplicação

npm i rss-parser@latest

Agora basta executar o leitor do feed dessa maneira, passando a URL como parâmetro:

const feed = await new RSSParser().parseURL(feedUrl);
for (const item of feed.items) {
	// Realizar a leitura aqui! Coloque a sua lógica de negócio
}

Nesse loop para fazer a leitura dos itens, você pode colocar a lógica que desejar. No nosso caso esse código está dentro do nosso BOT do discord e estamos trabalhando com um banco de dados MongoDB. A ideia é verificar se o post do blog já foi lido anteriormente. Se não foi lido, o bot pega os dados do post e envia como uma mensagem para um de nossos canais de texto.

Vamos verificar se o post já foi lido anteriormente. Se sim, faz a leitura do próximo post. Caso contrário, irá prosseguir para salvar no DB:

//...etc
const blogPost = await this.blogPostRepository.readOneByLink({
	link: item.link as string,
});
if (blogPost) continue;
const newBlogPost = this.buildBlogPost(item);
await this.blogPostRepository.create({
	blogPost: newBlogPost,
});
//...etc

Depois basta fazer a leitura do item do feed e mandar a mensagem para o canal apropriado:

const channelId = env.discord.blogChannelID;
if (!channelId)
	throw new Error("The blogChannelID config is not set correctly.");
    
const url = item.link as string;
const title = item.title as string;
const messagePayload = `⭐ 4Future tem post novo!\n➡️ [${title}](${url})`;
    
const channel = client.channels.cache.get(channelId!);
if (channel && channel.isTextBased()) {
	channel.send(messagePayload);
}

Aqui está o trecho do código que faz toda essa lógica:
Obs: Está super-comentado com o intuito didático, nossos códigos só têm comentários em trechos onde é estritamente necessário!

// >> The env file with the configurations
import * as env from "../../../../config/env/environment.js";
// >> This is the discord client (see discordX docs)
import { client } from "../../../../config/discordX.js";
// >> The Blog models to interact with the database
import {
    IBlogPostCreate,
    IBlogPostRepository,
} from "../../repository/index.js";
// >> The RSS parsed package we used
import RSSParser from "rss-parser";

 export class BlogCheckFeedUseCase {
    // Interface to decouple the database logic from this useCase:
    constructor(private blogPostRepository: IBlogPostRepository) {}

    // Entry point:
    async exec(): Promise<void> {
        const feedUrl = "https://4future.com.br/index.php/feed/";
        await this.readFeed(feedUrl);
        return;
    }

    async readFeed(url: string): Promise<void> {
        //1. Read the 4Future feed to obtain all the latest blog posts
        const feed = await new RSSParser().parseURL(url);

        for (const item of feed.items) {
            // 2. Check if the blog posts is already in the database
            const blogPost = await this.blogPostRepository.readOneByLink({
                link: item.link as string,
            });
            if (blogPost) continue;

            // 3. If not, save it in the database to that it won't be sent again
            const newBlogPost = this.buildBlogPost(item);
            await this.blogPostRepository.create({
                blogPost: newBlogPost,
            });

            // 4. And create and send the message
            await this.sendMessage(item);

            // 5. Wait 1 minute before sending the next message
            await new Promise((resolve) => setTimeout(resolve, 60000));
        }

        return;
    }

    /** Get the channel Id and sends the formated message */
    async sendMessage(item: RSSParser.Item): Promise<boolean> {
        // Obtain the channelId from the configurations
        const channelId = env.discord.blogChannelID;
        if (!channelId)
            throw new Error("The blogChannelID config is not set correctly.");

        // Setup the message to send to the text channel:
        const url = item.link as string;
        const title = item.title as string;
        const messagePayload = `⭐ 4Future tem post novo!\n➡️ [${title}](${url})`;

        // Send the message
        const channel = client.channels.cache.get(channelId!);
        if (channel && channel.isTextBased()) {
            channel.send(messagePayload);
        }
        return true;
    }

    buildBlogPost(item: RSSParser.Item): IBlogPostCreate {
        return {
            title: item.title as string,
            link: item.link as string,
            pubDate: item.pubDate as string,
            creator: item.creator as string,
            // content: item.content as string, // Not saving the contents to save space
            contentSnippet: item.contentSnippet as string,
            guid: item.guid as string,
            categories: item.categories as string[],
            date: item.isoDate as unknown as Date,
        };
    }
}

Precisamos ter um ponto de partida para que essa leitura do feed seja realizada. No nosso BOT, colocamos um cronJob que realiza a checagem por novos posts periodicamente. A initCron é chamada no início da execução da aplicação.
Você também poderia adicionar um comando para o BOT que chama o blogCheckFeedUseCase , por exemplo.

// >> The cronJobs package
import cron from "node-cron";
import { blogCheckFeedUseCase } from "../modules/blog/useCases/checkFeed/index.js";

export function initCron() {
    hourlyCheckBlog();
}

function hourlyCheckBlog() {
    // Every hour
    cron.schedule("0 * * * *", () => {
        blogCheckFeedUseCase.exec();
    });
}

Aqui o resultado final de como ficou no nosso canal do Discord.
Optei por simplesmente mandar o link (formatado em .md) pois o link-preview que o discord monta funciona muito bem.

O nosso BOT (chamado carinhosamente de DevID) possui algumas facilidades para nossa equipe de devs, como report de PR no DevOps, ainda está dando seus primeiros passos e está aprendendo novas funcionalidades.

Em breve iremos abrir o repositório do nosso BOT como public, aqui está o link:
https://github.com/BNPTI/DevID

Aguardo seu PR lá no GitHub!

Sobre Danilo Uema 4 Artigos
Desenvolvedor de sistemas senior Full Stack com conhecimentos em C#, TypeScript, SQL, NoSQL

Seja o primeiro a comentar

Faça um comentário

Seu e-mail não será divulgado.


*