Skip to content

Instantly share code, notes, and snippets.

@0yogue
Created April 18, 2025 03:33
Show Gist options
  • Select an option

  • Save 0yogue/8c541334880af7fdda269e3848fb8379 to your computer and use it in GitHub Desktop.

Select an option

Save 0yogue/8c541334880af7fdda269e3848fb8379 to your computer and use it in GitHub Desktop.
// Estrutura de extração de dados da Graph API do Meta usando Node.js
// Para Fluxos.co - Integração Facebook, Instagram e WhatsApp
// --- Dependências necessárias ---
// npm install facebook-nodejs-business-sdk axios dotenv winston mongoose express node-cron
// --- Configuração e inicialização ---
require('dotenv').config(); // Para variáveis de ambiente
const bizSdk = require('facebook-nodejs-business-sdk');
const axios = require('axios');
const winston = require('winston'); // Para logging
const cron = require('node-cron'); // Para agendamento
// Configuração da SDK do Facebook
const AdAccount = bizSdk.AdAccount;
const Page = bizSdk.Page;
const Lead = bizSdk.Lead;
const api = bizSdk.FacebookAdsApi.init(process.env.META_ACCESS_TOKEN);
const showDebugingInfo = true;
if (showDebugingInfo) {
api.setDebug(true);
}
// Logger
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
defaultMeta: { service: 'meta-data-extractor' },
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' }),
new winston.transports.Console()
],
});
// --- Utilitários Gerais ---
// Retry com backoff exponencial
async function retryWithBackoff(operation, maxRetries = 3, initialDelay = 1000) {
let retries = 0;
while (true) {
try {
return await operation();
} catch (error) {
retries++;
if (retries > maxRetries) {
throw error;
}
// Rate limit ou erro 4xx/5xx
if (error.status && error.status >= 400) {
const delay = initialDelay * Math.pow(2, retries - 1);
logger.warn(`Rate limit atingido, tentando novamente em ${delay}ms. Tentativa ${retries} de ${maxRetries}`);
await new Promise(resolve => setTimeout(resolve, delay));
} else {
throw error;
}
}
}
}
// Formatar datas para yyyy-mm-dd
function formatDate(date) {
return date.toISOString().split('T')[0];
}
// Normalizar IDs
function normalizeId(id, prefix = '') {
if (!id) return null;
if (id.startsWith(prefix)) return id;
return `${prefix}${id}`;
}
// Parse seguro para valores numéricos
function parseNumericValue(value, defaultValue = 0) {
const parsed = Number(value);
return isNaN(parsed) ? defaultValue : parsed;
}
// Validação de parâmetros do cliente
function validateParams(params) {
const { adAccountId, formIds, pageId, whatsappPhoneId, whatsappToken } = params;
const errors = [];
if (!adAccountId) {
errors.push('adAccountId é obrigatório');
} else if (!adAccountId.startsWith('act_')) {
errors.push('adAccountId deve começar com "act_"');
}
if (formIds && !Array.isArray(formIds)) {
errors.push('formIds deve ser um array');
}
if (!pageId) {
errors.push('pageId é obrigatório');
}
if (whatsappPhoneId && !whatsappToken) {
errors.push('whatsappToken é obrigatório quando whatsappPhoneId é fornecido');
}
return {
isValid: errors.length === 0,
errors
};
}
// --- 1. Extrator de Facebook/Instagram Ads ---
/**
* Extrai o maior conjunto possível de métricas de anúncios do Meta,
* priorizando as métricas essenciais para o negócio e exportando também todas as demais.
* Campos obrigatórios: impressões, CPM, CPC, CTR, cliques, conversões, etc.
* Documentação oficial: https://developers.facebook.com/docs/marketing-api/insights/parameters/v18.0
*/
async function extractAdsData(adAccountId, dateRange = { since: '2023-01-01', until: '2023-01-31' }) {
try {
const fields = [
// Métricas prioritárias
'campaign_name',
'impressions',
'cpm',
'unique_outbound_clicks',
'outbound_clicks_ctr',
'clicks',
'cpc',
'website_ctr',
'actions',
'action_values', // Valor das ações (ex: valor das compras)
'cost_per_action_type', // Custo por tipo de ação
'conversions',
'spend',
// Métricas adicionais sugeridas
// ... (demais campos)
];
// ... (restante da função)
} catch (error) {
logger.error(`Erro ao extrair dados de anúncios: ${error.message}`);
return [];
}
}
// ... (demais funções e módulos)
// --- 6. Exportação de Dados ---
function exportData(clientName, data) {
const fs = require('fs');
const path = require('path');
const dir = './data';
console.log('Exportando dados para:', dir);
logger.info('Exportando dados para: ' + dir);
// Criar diretório se não existir
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
}
// Criar diretório para o cliente
const clientDir = path.join(dir, clientName);
if (!fs.existsSync(clientDir)) {
fs.mkdirSync(clientDir);
}
// Criar subdiretório para a data atual
const today = new Date();
const dateDir = path.join(clientDir, today.toISOString().split('T')[0]);
if (!fs.existsSync(dateDir)) {
fs.mkdirSync(dateDir);
}
// Exportar cada tipo de dado para um arquivo JSON separado
for (const [dataType, dataContent] of Object.entries(data)) {
const filePath = path.join(dateDir, `${dataType}.json`);
console.log('Salvando arquivo em:', filePath);
logger.info('Salvando arquivo em: ' + filePath);
fs.writeFileSync(filePath, JSON.stringify(dataContent, null, 2));
logger.info(`Dados exportados para: ${filePath}`);
}
// Gerar e exportar relatório de métricas prioritárias se tiver dados de anúncios
if (data.adsData && data.adsData.length > 0) {
const priorityReport = generatePriorityMetricsReport(clientName, data.adsData);
if (priorityReport) {
// Exportar como JSON
const reportJsonPath = path.join(dateDir, 'priority_metrics_report.json');
console.log('Salvando arquivo em:', reportJsonPath);
logger.info('Salvando arquivo em: ' + reportJsonPath);
fs.writeFileSync(reportJsonPath, JSON.stringify(priorityReport, null, 2));
// Exportar também como CSV para fácil importação em Excel
const { Parser } = require('json2csv');
// Exportar resumo
const summaryFields = Object.keys(priorityReport.summary);
const summaryParser = new Parser({ fields: summaryFields });
const summaryCsv = summaryParser.parse([priorityReport.summary]);
const summaryCsvPath = path.join(dateDir, 'priority_metrics_summary.csv');
console.log('Salvando arquivo em:', summaryCsvPath);
logger.info('Salvando arquivo em: ' + summaryCsvPath);
fs.writeFileSync(summaryCsvPath, summaryCsv);
// Exportar dados por campanha
if (priorityReport.campaigns.length > 0) {
const campaignFields = Object.keys(priorityReport.campaigns[0]);
const campaignsParser = new Parser({ fields: campaignFields });
const campaignsCsv = campaignsParser.parse(priorityReport.campaigns);
const campaignsCsvPath = path.join(dateDir, 'priority_metrics_by_campaign.csv');
console.log('Salvando arquivo em:', campaignsCsvPath);
logger.info('Salvando arquivo em: ' + campaignsCsvPath);
fs.writeFileSync(campaignsCsvPath, campaignsCsv);
}
logger.info(`Relatório de métricas prioritárias exportado para: ${reportJsonPath}`);
}
}
// Criar um arquivo index.html simples para visualização dos dados
const indexHtml = `
<!DOCTYPE html>
<html>
<head>
<title>Dados Extraídos - ${clientName}</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
h1 { color: #333; }
.file-list { margin: 20px 0; }
.file-item { margin: 10px 0; }
a { color: #0066cc; text-decoration: none; }
a:hover { text-decoration: underline; }
</style>
</head>
<body>
<h1>Dados Extraídos - ${clientName}</h1>
<p>Extração realizada em: ${new Date().toLocaleString()}</p>
<div class="file-list">
<h2>Arquivos Disponíveis:</h2>
${fs.readdirSync(dateDir).map(file => `
<div class="file-item">
<a href="file://${path.join(dateDir, file)}">${file}</a>
</div>
`).join('')}
</div>
</body>
</html>
`;
const indexPath = path.join(dateDir, 'index.html');
console.log('Salvando arquivo em:', indexPath);
logger.info('Salvando arquivo em: ' + indexPath);
fs.writeFileSync(indexPath, indexHtml);
logger.info(`Página de índice criada em: ${indexPath}`);
return dateDir; // Retorna o caminho do diretório onde os dados foram salvos
}
// --- TESTE MANUAL DE EXPORTAÇÃO ---
if (process.argv.includes('--test-export')) {
const exportData = module.exports ? module.exports.exportData : global.exportData;
if (typeof exportData !== 'function') {
console.error('Função exportData não encontrada!');
process.exit(1);
}
const testData = {
adsData: [{ id: 1, name: 'Teste Ad', impressions: 1000 }],
conversionData: [{ id: 1, type: 'purchase', value: 200 }],
audienceData: [{ id: 1, segment: '18-24', size: 500 }],
customAudiences: [{ id: 1, name: 'Custom Audience 1' }],
urlParameters: [{ id: 1, param: 'utm_source', value: 'test' }]
};
const dir = exportData('TEST_CLIENTE', testData);
console.log('Exportação de teste concluída. Diretório:', dir);
process.exit(0);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment