Como criei uma newsletter sobre sneakers automática usando Cloud Functions e Inteligência artificial
Criar uma newsletter automática que consome feeds RSS e resume o conteúdo sobre sneakers foi um projeto interessante que combinou diversas tecnologias, incluindo Cloud Functions, RSS parsing, e processamento de linguagem natural com OpenAI. Aqui está uma visão técnica detalhada de como fiz isso. Caso você queria se inscrever para receber o conteúdo é só acessar https://sneakerpedia.com.br
Estrutura do Projeto
- Cloud Functions para agendamento: Utilizei Cloud Functions do Firebase para agendar a execução diária da geração da newsletter.
- Consumo de Feeds RSS: Feeds RSS são consumidos e processados para extrair as notícias mais recentes, usei o feed ao invés de usar o webscrapping, ficou mais simples e eu ainda consigo economizar nos tokens :)
- Resumo e Tradução de Conteúdo: Utilizamos a API do OpenAI para resumir e traduzir os títulos das notícias.
- Geração da Newsletter: O conteúdo é formatado em Markdown para criar a newsletter.
- Publicação no Instagram: A primeira notícia é publicada automaticamente no Instagram.
Um pouco do código fonte
Este arquivo contém a função agendada que inicia o processo de criação da newsletter diariamente à meia-noite.
const { onSchedule } = require("firebase-functions/v2/scheduler");
const createBroadcast = require("./lib/createBroadcast");
const config = require("./config/config");
exports.helloWorld = onSchedule("every day 00:00", () =>
createBroadcast(config.feeds)
);
Este arquivo contém a lógica principal para consumir feeds RSS, resumir o conteúdo, formatar a newsletter e publicá-la.
const fs = require("fs");
const path = require("path");
const Parser = require("rss-parser");
const {
translateTitle,
summarizeNews,
generateIntroduction,
createNewsletterTitle,
checkNewsContent,
} = require("./ai");
const { publisInstagram } = require("./publishInstagram");
const parser = new Parser();
const imageRegex = /<img\s+[^>]*src="([^"]+)"[^>]*>/i;
const placeholderImageUrl = "https://via.placeholder.com/150";
const formatCategoryNews = async (news) => {
try {
const imageMarkdown = `\n![Imagem](${news.image})\n`;
return `
## ${news.title}
${imageMarkdown}
${await checkNewsContent(news.summary)}
`;
} catch (error) {
console.error("Erro ao formatar categoria de notícias:", error);
return null;
}
};
const getNewsFromRSSFeeds = async (feeds) => {
let allNews = [];
for (const feedOptions of feeds) {
try {
const feed = await parser.parseURL(feedOptions.feedUrl);
const newsFromFeedPromises = feed.items.slice(0, 3).map(async (item) => {
const imageMatch = item[feedOptions.contentIn].match(imageRegex);
const imageUrl = imageMatch ? imageMatch[1] : placeholderImageUrl;
return {
title: await translateTitle(item.title),
link: item.link,
content: item[feedOptions.contentIn],
image: imageUrl,
summary: await summarizeNews(item[feedOptions.contentIn]),
name: feedOptions.name,
alreadySent: false,
};
});
const newsFromFeed = await Promise.all(newsFromFeedPromises);
allNews = allNews.concat(newsFromFeed);
} catch (error) {
console.error(`Erro ao buscar notícias do feed ${feedOptions.name}:`, error);
}
}
return allNews;
};
const createNewsletter = async (feeds) => {
const allNews = await getNewsFromRSSFeeds(feeds);
try {
const titles = allNews.map((item) => item.title);
const newsletterTitle = await createNewsletterTitle(titles);
const introduction = await generateIntroduction(allNews);
let formattedNewsletter = `# ${newsletterTitle}\n\n${introduction}\n`;
for (const item of allNews) {
const formattedCategory = await formatCategoryNews(item);
formattedNewsletter += formattedCategory;
}
return {
formattedNewsletter,
newsletterTitle,
allNews,
};
} catch (error) {
console.error("Erro ao formatar newsletter:", error);
return null;
}
};
const createBroadcast = async (feeds) => {
try {
const newsletter = await createNewsletter(feeds);
if (!newsletter) {
throw new Error("Newsletter não pôde ser criada");
}
const filePath = path.join(__dirname, "newsletter.md");
fs.writeFileSync(filePath, newsletter.formattedNewsletter);
console.log(`Newsletter criada com sucesso: ${filePath}`);
console.log(`Publicando no instagram a primeira noticia`);
await publisInstagram(
newsletter.allNews[0].title,
newsletter.allNews[0].image,
newsletter.allNews[0].summary
);
} catch (error) {
console.error("Erro ao criar broadcast:", error);
}
};
module.exports = createBroadcast;
Optei por deixar apenas a criação de um markdown, para usar no behiive, plataforma bem bacana de newsletter que conheci esses dias. Mas poderiam ter outros outputs e até mesmo uma criação direta com a API, infelizmente o behiive não tem o recurso para criação de posts :/
Fonte: https://sneakerpedia.com.br