from fastapi import FastAPI from fastapi.responses import HTMLResponse from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.triggers.cron import CronTrigger import requests import xml.etree.ElementTree as ET import google.generativeai as genai import resend from datetime import datetime, timedelta import os import time from bs4 import BeautifulSoup import pytz import traceback app = FastAPI() # ─── CONFIGURAÇÃO ─────────────────────────────────────────────── RESEND_API_KEY = os.getenv("RESEND_API_KEY") GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY") # ✅ DOMÍNIO PRÓPRIO CONFIGURADO EMAIL_REMETENTE = os.getenv("EMAIL_REMETENTE", "falecom@mariomello.com.br") EMAIL_DESTINO = os.getenv("EMAIL_DESTINO", "marioluiz_mello@hotmail.com") TZ_BRASILIA = pytz.timezone("America/Sao_Paulo") # ─── VARIÁVEL GLOBAL DE ESTADO ────────────────────────────────── ultimo_envio = {"status": "Nunca executado", "data": None, "atos": 0, "log": []} def log(msg: str): timestamp = datetime.now(TZ_BRASILIA).strftime("%H:%M:%S") entry = f"[{timestamp}] {msg}" ultimo_envio["log"].append(entry) print(entry) if len(ultimo_envio["log"]) > 100: ultimo_envio["log"] = ultimo_envio["log"][-100:] # ─── VALIDAÇÃO ───────────────────────────────────────────────── log("=" * 50) log("🚀 AGENTE SEPAF INICIANDO...") log(f"📧 Remetente configurado: {EMAIL_REMETENTE}") log("=" * 50) if not RESEND_API_KEY: log("❌ CRÍTICO: RESEND_API_KEY não configurada!") else: log("✅ RESEND_API_KEY OK") if not GOOGLE_API_KEY: log("❌ CRÍTICO: GOOGLE_API_KEY não configurada!") else: log("✅ GOOGLE_API_KEY OK") # ─── LEITURA DO emails.txt ────────────────────────────────────── lista_emails = [] EMAILS_TXT_PATHS = [ "/mnt/agents/upload/emails.txt", "./emails.txt", "/app/emails.txt", "emails.txt", ] for path in EMAILS_TXT_PATHS: if os.path.exists(path): try: with open(path, "r", encoding="utf-8") as f: linhas = f.readlines() for linha in linhas: email = linha.strip() if email and "@" in email: lista_emails.append(email) log(f"📄 emails.txt encontrado em: {path}") log(f" {len(lista_emails)} e-mails: {lista_emails}") break except Exception as e: log(f"⚠️ Erro ao ler {path}: {e}") if not lista_emails: lista_emails = [EMAIL_DESTINO] log(f"📧 Usando e-mail padrão: {EMAIL_DESTINO}") resend.api_key = RESEND_API_KEY # ─── FUNÇÃO DE TESTE DO RESEND ───────────────────────────────── def testar_resend(): if not RESEND_API_KEY: return "❌ RESEND_API_KEY não configurada" try: log("🧪 TESTANDO RESEND...") params = { "from": EMAIL_REMETENTE, "to": lista_emails[0], "subject": "🧪 TESTE SEPAF - Verificação de Domínio Próprio", "html": f"""
Se você recebeu este e-mail, o domínio {EMAIL_REMETENTE} está verificado e funcionando corretamente no Resend.
Horário do teste: {datetime.now(TZ_BRASILIA).strftime('%d/%m/%Y %H:%M:%S')}
""", } resultado = resend.Emails.send(params) headers = resultado.get("http_headers", {}) daily = headers.get("x-resend-daily-quota", "?") monthly = headers.get("x-resend-monthly-quota", "?") log(f"✅ TESTE: ID={resultado.get('id')}") log(f" 📊 Cota: {daily}/dia | {monthly}/mês") return f"Teste enviado! ID: {resultado.get('id')} | Cota: {daily}/dia, {monthly}/mês" except Exception as e: log(f"❌ TESTE FALHOU: {e}") log(traceback.format_exc()) return f"❌ Falha: {e}" # ─── FUNÇÕES AUXILIARES ───────────────────────────────────────── def extrair_texto_pagina(url: str) -> str: try: headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"} resposta = requests.get(url, headers=headers, timeout=15) resposta.raise_for_status() soup = BeautifulSoup(resposta.text, "html.parser") for tag in soup(["script", "style", "nav", "footer", "header"]): tag.decompose() texto = soup.get_text(separator=" ", strip=True) return texto[:4000] except Exception as e: log(f"❌ Erro ao raspar {url}: {e}") return "" def executar_agente_diario(): global ultimo_envio agora_brasilia = datetime.now(TZ_BRASILIA) log("=" * 50) log(f"🚀 INICIANDO AGENTE em {agora_brasilia}") log("=" * 50) hoje = agora_brasilia data_fim = hoje.strftime("%d/%m/%Y") data_inicio = (hoje - timedelta(days=7)).strftime("%d/%m/%Y") url_rss = ( f"https://diariomunicipal.sc.gov.br/?r=site/portal" f"&codigoEntidade=173&dataInicial={data_inicio}" f"&dataFinal={data_fim}&view=rss" ) log(f"🔗 RSS: {url_rss}") try: headers = {"User-Agent": "Mozilla/5.0"} resposta_rss = requests.get(url_rss, headers=headers, timeout=20) resposta_rss.raise_for_status() root = ET.fromstring(resposta_rss.content) itens = root.findall(".//item") log(f"📰 {len(itens)} itens no RSS") except Exception as e: msg = f"❌ ERRO RSS/XML: {e}" log(msg) ultimo_envio = {**ultimo_envio, "status": msg, "data": agora_brasilia.isoformat(), "atos": 0} return msg if not itens: msg = f"⚠️ VAZIO: Nenhuma publicação entre {data_inicio} e {data_fim}." log(msg) ultimo_envio = {**ultimo_envio, "status": msg, "data": agora_brasilia.isoformat(), "atos": 0} return msg # Raspagem lista_final = [] for idx, item in enumerate(itens): titulo_elem = item.find("title") link_elem = item.find("link") titulo = titulo_elem.text.strip() if titulo_elem is not None and titulo_elem.text else "Título não disponível" link_ato = link_elem.text.strip() if link_elem is not None and link_elem.text else "" log(f" [{idx+1}/{len(itens)}] {titulo[:60]}...") texto_ato = extrair_texto_pagina(link_ato) if link_ato else "" lista_final.append({"titulo": titulo, "link": link_ato, "texto": texto_ato}) time.sleep(0.5) log(f"✅ {len(lista_final)} atos processados") if not lista_final: msg = "⚠️ Nenhum ato válido." ultimo_envio = {**ultimo_envio, "status": msg, "data": agora_brasilia.isoformat(), "atos": 0} return msg # Gemini if not GOOGLE_API_KEY: msg = "❌ GOOGLE_API_KEY não configurada." log(msg) ultimo_envio = {**ultimo_envio, "status": msg, "data": agora_brasilia.isoformat(), "atos": 0} return msg log("🤖 Enviando para Gemini...") genai.configure(api_key=GOOGLE_API_KEY) model = genai.GenerativeModel("gemini-2.5-flash") bloco_dados = "" for i, a in enumerate(lista_final): bloco_dados += f"--- ATO {i+1} ---\nTÍTULO: {a['titulo']}\nLINK: {a['link']}\nTEXTO: {a['texto']}\n\n" prompt = f""" Você é um Auditor Analítico da SEPAF Navegantes. Recebemos {len(lista_final)} atos oficiais. Crie um relatório HTML profissional. Para cada ato: - Título Original - Resumo executivo (Nomes, CNPJs, Valores R$, decisão) - 🔗 CLIQUE AQUI PARA VER MATÉRIA COMPLETA Use CSS inline. Devolva APENAS HTML puro, sem Markdown. DADOS: {bloco_dados} """ try: response = model.generate_content(prompt) resumo_html = response.text.replace("```html", "").replace("```", "").strip() log("✅ Gemini OK") except Exception as e: msg = f"❌ ERRO GEMINI: {e}" log(msg) log(traceback.format_exc()) ultimo_envio = {**ultimo_envio, "status": msg, "data": agora_brasilia.isoformat(), "atos": 0} return msg # Resend if not RESEND_API_KEY: msg = "❌ RESEND_API_KEY não configurada." log(msg) ultimo_envio = {**ultimo_envio, "status": msg, "data": agora_brasilia.isoformat(), "atos": 0} return msg log(f"📧 Enviando para {len(lista_emails)} destinatários...") emails_enviados = 0 emails_falhos = 0 cota_info = "N/A" for email_destino in lista_emails: try: log(f" 📤 Enviando para {email_destino}...") params = { "from": EMAIL_REMETENTE, "to": email_destino, "subject": f"🏛️ SEPAF: Diário Navegantes ({data_inicio} a {data_fim})", "html": resumo_html, } resultado = resend.Emails.send(params) headers = resultado.get("http_headers", {}) daily = headers.get("x-resend-daily-quota", "?") monthly = headers.get("x-resend-monthly-quota", "?") cota_info = f"{daily}/dia, {monthly}/mês" log(f" ✅ ID: {resultado.get('id')} | Cota: {cota_info}") emails_enviados += 1 if daily != "?" and int(daily) <= 0: log(" 🚨 COTA DIÁRIA ESGOTADA!") break except Exception as e: log(f" ❌ Falha para {email_destino}: {e}") emails_falhos += 1 if emails_enviados > 0: msg = f"✅ {emails_enviados}/{len(lista_emails)} enviados. Cota: {cota_info}" else: msg = f"❌ Nenhum e-mail enviado. {emails_falhos} falhas. Cota: {cota_info}" log(msg) ultimo_envio = { **ultimo_envio, "status": msg, "data": agora_brasilia.isoformat(), "atos": len(lista_final), "periodo": f"{data_inicio} a {data_fim}", "emails_enviados": emails_enviados, "emails_falhos": emails_falhos, "cota": cota_info } return msg # ─── AGENDADOR ────────────────────────────────────────────────── scheduler = BackgroundScheduler(timezone=TZ_BRASILIA) scheduler.add_job( executar_agente_diario, trigger=CronTrigger(hour=11, minute=30), id="agente_diario", name="Agente Diário Oficial", replace_existing=True, ) scheduler.start() log("⏰ Agendador: 11:30 BRT") # ─── ROTAS ────────────────────────────────────────────────────── @app.get("/", response_class=HTMLResponse) def home(): status = ultimo_envio.get("status", "Nunca executado") data = ultimo_envio.get("data", "—") atos = ultimo_envio.get("atos", 0) emails_env = ultimo_envio.get("emails_enviados", 0) emails_fal = ultimo_envio.get("emails_falhos", 0) cota = ultimo_envio.get("cota", "N/A") log_html = "RSS Público + Gemini 2.5 Flash + Resend | ⏰ 10:00 BRT
📧 Verifique a caixa de entrada (e SPAM) de: {lista_emails[0] if lista_emails else 'Nenhum'}