import os
import logging
import json
import requests
import importlib.util
from pathlib import Path
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from google import genai
from google.genai import types
from datetime import datetime
from zoneinfo import ZoneInfo
import locale
import re
import asyncio
from typing import Optional, Dict, Any
# Configurar logging
logger = logging.getLogger(__name__)
router = APIRouter()
class NewsRequest(BaseModel):
content: str
file_id: str = None # Agora opcional
class NewsResponse(BaseModel):
title: str
subhead: str
content: str
title_instagram: str
content_instagram: str
sources_info: Optional[Dict[str, Any]] = None # Informações das fontes geradas
# Referência ao diretório de arquivos temporários
TEMP_DIR = Path("/tmp")
def load_searchterm_module():
"""Carrega o módulo searchterm.py dinamicamente"""
try:
# Procura o arquivo searchterm.py em diferentes locais
searchterm_path = Path(__file__).parent / "searchterm.py"
if not searchterm_path.exists():
# Tenta outros caminhos possíveis
possible_paths = [
Path(__file__).parent.parent / "searchterm.py",
Path("./searchterm.py"),
Path("../searchterm.py")
]
for path in possible_paths:
if path.exists():
searchterm_path = path
break
else:
logger.error("searchterm.py não encontrado em nenhum dos caminhos")
return None
spec = importlib.util.spec_from_file_location("searchterm", searchterm_path)
searchterm_module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(searchterm_module)
logger.info(f"Módulo searchterm.py carregado com sucesso: {searchterm_path}")
return searchterm_module
except Exception as e:
logger.error(f"Erro ao carregar searchterm.py: {str(e)}")
return None
# Carrega o módulo na inicialização
searchterm_module = load_searchterm_module()
async def generate_sources_from_content(content: str) -> Optional[str]:
"""
Gera fontes usando o módulo searchterm baseado no conteúdo da notícia
"""
try:
if not searchterm_module:
logger.error("Módulo searchterm não carregado")
return None
logger.info(f"Gerando fontes para conteúdo: {len(content)} caracteres")
# Prepara o payload para o searchterm
payload = {"context": content}
# Chama a função search_terms do módulo searchterm
# Simula uma requisição FastAPI criando um objeto com o método necessário
result = await searchterm_module.search_terms(payload)
if result and "file_info" in result:
file_id = result["file_info"]["file_id"]
logger.info(f"Fontes geradas com sucesso. File ID: {file_id}")
logger.info(f"Total de resultados: {result.get('total_results', 0)}")
logger.info(f"Termos gerados: {len(result.get('generated_terms', []))}")
return file_id
else:
logger.error("Resultado inválido do searchterm")
return None
except Exception as e:
logger.error(f"Erro ao gerar fontes: {str(e)}")
return None
def get_brazilian_date_string():
"""
Retorna a data atual formatada em português brasileiro.
Implementa fallbacks robustos para diferentes sistemas operacionais.
"""
try:
# Tenta configurar o locale brasileiro
locale_variants = [
'pt_BR.UTF-8',
'pt_BR.utf8',
'pt_BR',
'Portuguese_Brazil.1252',
'Portuguese_Brazil',
'pt_BR.ISO8859-1',
]
locale_set = False
for loc in locale_variants:
try:
locale.setlocale(locale.LC_TIME, loc)
locale_set = True
break
except locale.Error:
continue
if not locale_set:
locale.setlocale(locale.LC_TIME, '')
now = datetime.now(ZoneInfo("America/Sao_Paulo"))
# Dicionários para tradução manual (fallback)
meses = {
1: 'janeiro', 2: 'fevereiro', 3: 'março', 4: 'abril',
5: 'maio', 6: 'junho', 7: 'julho', 8: 'agosto',
9: 'setembro', 10: 'outubro', 11: 'novembro', 12: 'dezembro'
}
dias_semana = {
0: 'segunda-feira', 1: 'terça-feira', 2: 'quarta-feira',
3: 'quinta-feira', 4: 'sexta-feira', 5: 'sábado', 6: 'domingo'
}
try:
if locale_set:
try:
date_string = now.strftime("%-d de %B de %Y (%A)")
except ValueError:
try:
date_string = now.strftime("%#d de %B de %Y (%A)")
except ValueError:
date_string = now.strftime("%d de %B de %Y (%A)")
if date_string.startswith('0'):
date_string = date_string[1:]
date_string = date_string.replace(date_string.split('(')[1].split(')')[0],
date_string.split('(')[1].split(')')[0].lower())
else:
dia = now.day
mes = meses[now.month]
ano = now.year
dia_semana = dias_semana[now.weekday()]
date_string = f"{dia} de {mes} de {ano} ({dia_semana})"
except Exception:
dia = now.day
mes = meses[now.month]
ano = now.year
dia_semana = dias_semana[now.weekday()]
date_string = f"{dia} de {mes} de {ano} ({dia_semana})"
return date_string
except Exception:
now = datetime.now(ZoneInfo("America/Sao_Paulo"))
date_string = now.strftime("%d de %B de %Y")
return date_string
def load_sources_file(file_id: str) -> str:
"""
Carrega o arquivo de fontes pelo ID do arquivo temporário.
"""
try:
# Constrói o caminho do arquivo
file_path = TEMP_DIR / f"fontes_{file_id}.txt"
# Verifica se o arquivo existe
if not file_path.exists():
raise HTTPException(
status_code=404,
detail=f"Arquivo temporário não encontrado ou expirado: {file_id}"
)
# Lê o conteúdo do arquivo
with open(file_path, 'r', encoding='utf-8') as f:
file_content = f.read()
# Se for um JSON, extrai os dados; caso contrário, retorna o conteúdo direto
try:
data = json.loads(file_content)
# Se contém 'results', formata os dados para o Gemini
if 'results' in data and isinstance(data['results'], list):
formatted_content = ""
for idx, result in enumerate(data['results'], 1):
formatted_content += f"\n--- FONTE {idx} ---\n"
formatted_content += f"Termo: {result.get('term', 'N/A')}\n"
formatted_content += f"URL: {result.get('url', 'N/A')}\n"
formatted_content += f"Idade: {result.get('age', 'N/A')}\n"
formatted_content += f"Conteúdo:\n{result.get('text', 'N/A')}\n"
formatted_content += "-" * 50 + "\n"
return formatted_content
else:
return file_content
except json.JSONDecodeError:
# Se não for JSON válido, retorna o conteúdo como texto
return file_content
except FileNotFoundError:
raise HTTPException(
status_code=404,
detail=f"Arquivo temporário não encontrado: {file_id}"
)
except PermissionError:
raise HTTPException(
status_code=500,
detail=f"Erro de permissão ao acessar arquivo: {file_id}"
)
except Exception as e:
logger.error(f"Erro ao carregar arquivo de fontes {file_id}: {e}")
raise HTTPException(
status_code=500,
detail=f"Erro ao carregar arquivo de fontes: {str(e)}"
)
def extract_text_from_response(response):
"""
Extrai o texto da resposta de forma robusta com debug.
"""
logger.info(f"Tipo da resposta: {type(response)}")
# Método 1: Tentar acessar response.text diretamente
try:
text_content = getattr(response, 'text', None)
if text_content:
logger.info(f"Texto extraído via response.text: {len(text_content)} caracteres")
return text_content
else:
logger.info("response.text existe mas está vazio/None")
except Exception as e:
logger.error(f"Erro ao acessar response.text: {e}")
# Método 2: Verificar candidates
if hasattr(response, 'candidates') and response.candidates:
logger.info(f"Encontrados {len(response.candidates)} candidates")
for i, candidate in enumerate(response.candidates):
logger.info(f"Processando candidate {i}")
# Verificar se tem content
if hasattr(candidate, 'content') and candidate.content:
content = candidate.content
logger.info(f"Candidate {i} tem content")
# Verificar se tem parts
if hasattr(content, 'parts') and content.parts:
try:
parts_list = list(content.parts)
logger.info(f"Content tem {len(parts_list)} parts")
response_text = ""
for j, part in enumerate(parts_list):
logger.info(f"Processando part {j}, tipo: {type(part)}")
# Tentar várias formas de acessar o texto
part_text = None
if hasattr(part, 'text'):
part_text = getattr(part, 'text', None)
if part_text:
logger.info(f"Part {j} tem texto: {len(part_text)} caracteres")
response_text += part_text
else:
logger.info(f"Part {j} não tem texto ou está vazio")
if response_text:
return response_text
except Exception as e:
logger.error(f"Erro ao processar parts do candidate {i}: {e}")
return ""
@router.post("/rewrite-news", response_model=NewsResponse)
async def rewrite_news(news: NewsRequest):
"""
Endpoint para reescrever notícias usando o modelo Gemini.
Se file_id não for fornecido, gera automaticamente as fontes usando o conteúdo.
"""
try:
# Verificar API key
api_key = os.environ.get("GEMINI_API_KEY")
if not api_key:
raise HTTPException(status_code=500, detail="API key não configurada")
sources_info = None
# Se file_id não foi fornecido, gera fontes automaticamente
if not news.file_id:
logger.info("File ID não fornecido, gerando fontes automaticamente...")
generated_file_id = await generate_sources_from_content(news.content)
if generated_file_id:
news.file_id = generated_file_id
sources_info = {
"generated": True,
"file_id": generated_file_id,
"message": "Fontes geradas automaticamente a partir do conteúdo"
}
logger.info(f"Fontes geradas automaticamente. File ID: {generated_file_id}")
else:
logger.warning("Não foi possível gerar fontes automaticamente, prosseguindo sem fontes")
sources_info = {
"generated": False,
"message": "Não foi possível gerar fontes automaticamente"
}
else:
sources_info = {
"generated": False,
"file_id": news.file_id,
"message": "Usando file_id fornecido"
}
# Carregar arquivo de fontes se disponível
sources_content = ""
if news.file_id:
try:
sources_content = load_sources_file(news.file_id)
logger.info(f"Fontes carregadas: {len(sources_content)} caracteres")
except HTTPException as e:
logger.warning(f"Erro ao carregar fontes: {e.detail}")
sources_content = ""
client = genai.Client(api_key=api_key)
model = "gemini-2.5-pro"
# Obter data formatada
date_string = get_brazilian_date_string()
# Instruções do sistema (suas instruções originais aqui)
# Instruções do sistema
SYSTEM_INSTRUCTIONS = f"""
Você é um jornalista brasileiro, escrevendo para portais digitais. Sua missão é transformar notícias internacionais em matérias originais, detalhadas e atualizadas para o público brasileiro. Sempre use a notícia-base como ponto de partida, mas consulte o arquivo fontes.txt para extrair todas as informações relevantes, complementando fatos, contexto, dados e antecedentes. Não invente informações; na dúvida, não insira.
Seu estilo de escrita deve ser direto, claro e conversacional, sem jargões ou floreios desnecessários. Frases curtas e bem estruturadas, parágrafos segmentados para leitura digital e SEO. Evite repetições, clichês e generalizações.
Evite frases redundantes ou genéricas como:
- "Destacando como a experiência pode ser um divisor de águas profissional"
- "Reafirma a força criativa do país no cenário global"
- "A revelação contextualizou não apenas sua performance na dança, mas também"
O conteúdo deve priorizar clareza, contexto e completude:
- Comece com a informação mais relevante e específica.
- Contextualize causas, consequências e conexões com outros acontecimentos.
- Inclua dados, datas, lançamentos e fontes confiáveis.
- Use citações, títulos de obras e nomes próprios quando pertinentes.
- Finalize sempre com fatos concretos, nunca com opinião genérica.
Formato da matéria:
Ed Helms nunca escondeu o fato de que sua participação em Se Beber, Não Case! foi um choque cultural, especialmente para seus pais. Em uma entrevista recente ao podcast de Ted Danson, Where Everybody Knows Your Name, o ator falou sobre a ansiedade que sentiu ao imaginar a reação da família à comédia para maiores que o transformou em astro de cinema.
Helms, que foi criado em um lar sulista com valores socialmente conservadores, revelou que, embora o ambiente fosse politicamente progressista, algumas situações, como dentes arrancados, casamentos embriagados e até tigres no banheiro, eram muito diferentes do que seus pais consideravam apropriado. O ator brincou: "Não foi pra isso que me criaram", fazendo alusão ao enredo caótico do filme de 2009. Ele acrescentou que, embora seus pais já tivessem assistido a algumas de suas performances em programas como The Daily Show e The Office, o que ajudou a criar certa tolerância, o filme ainda o deixava nervoso.
Estrelando sua primeira grande produção, Helms levou os pais para a estreia quando tinha 35 anos. No entanto, foi surpreendido ao ver sua mãe chorando quando as luzes se acenderam. "Pensei: 'Pronto. Acabei de partir o coração da minha mãe'", recordou. O momento de tensão, porém, durou pouco: ela o tranquilizou dizendo que o filme havia sido hilário.
Se Beber, Não Case!, dirigido por Todd Phillips, foi um sucesso comercial, arrecadando aproximadamente 469 milhões de dólares em todo o mundo e se tornando a comédia para maiores de classificação indicativa de maior bilheteria até então. A popularidade do filme resultou em duas sequências, lançadas em 2011 e 2013, e consolidou o "bando de lobos" formado por Helms, Bradley Cooper e Zach Galifianakis como um dos times cômicos mais icônicos do cinema moderno.
Sobre a possibilidade de um quarto filme, Bradley Cooper afirmou em 2023 que toparia participar sem hesitar, principalmente pela chance de reencontrar colegas e diretor. Ainda assim, reconheceu que o projeto é improvável, já que Phillips está atualmente focado em empreendimentos de maior escala, como a série de filmes Coringa.
A equipe original de documentaristas de "Insane Daily Life at Dunder Mifflin" voltou ao trabalho, desta vez mudando para uma nova história, três anos após o fim de "The Office". Após uma década de espera, o derivado da amada série de comédia finalmente saiu do papel e será lançado em 4 de setembro de 2025. O nome do derivado é "The Paper" e estará disponível na plataforma de streaming Peacock.
A trama agora se desloca da fictícia Scranton, Pensilvânia, para o escritório de um jornal histórico, porém problemático, localizado no meio-oeste dos Estados Unidos, focando em um jornal em dificuldades na região. A equipe busca uma nova história após cobrir a vida de Michael Scott e Dwight Schrute. Agora, a equipe acompanha o Toledo Truth Teller, um jornal em Toledo, Ohio, e o editor que tenta reviver o jornal com a ajuda de repórteres voluntários.
O novo elenco conta com Domhnall Gleeson, ator irlandês famoso por "Ex Machina" e "Questão de Tempo", ao lado da atriz italiana Sabrina Impacciatore, que ganhou amplo reconhecimento por seu papel na segunda temporada de "The White Lotus". Gleeson interpreta o novo editor otimista do jornal, enquanto Impacciatore atua como gerente de redação.
Nas entrevistas mais recentes, Gleeson tenta se distanciar das comparações com o gerente da Dunder Mifflin. "Acho que se você tentar competir com o que Steve [Carell] ou Ricky Gervais fizeram, seria um enorme erro," enfatizou o ator, visando construir uma persona totalmente nova. Ele também revelou ter recebido um tipo de conselho de John Krasinski e até de Steve Carell para aceitar o papel, especialmente porque se tratava de um projeto de Greg Daniels.
Como "The Paper" está reintroduzindo os personagens originais, os fãs de longa data da série parecem estar encantados, já que também traz Oscar Nuñez reprisando seu papel como o contador Oscar Martinez. Oscar, que estava iniciando uma carreira política em "The Office", agora parece ter se mudado para Toledo. "Eu disse ao Sr. Greg Daniels que, se Oscar voltasse, ele provavelmente estaria morando em uma cidade mais agitada e cosmopolita. Greg me ouviu e mudou Oscar para Toledo, Ohio, que tem três vezes a população de Scranton. Então, foi bom ser ouvido", brincou Nuñez durante um evento da NBCUniversal.
Greg Daniels, que anteriormente adaptou "The Office" para o público americano, está em parceria com Michael Koman, cocriador de "Nathan for You", para este novo projeto. Koman e Daniels, junto com Ricky Gervais e Stephen Merchant, criadores da série britânica original, formam a equipe de produção executiva.
A primeira temporada de "The Paper" será dividida em dez episódios. Nos Estados Unidos, os quatro primeiros episódios estarão disponíveis para streaming em 4 de setembro. Depois disso, os episódios restantes serão lançados no formato de dois episódios por semana, com um total de seis episódios liberados até o final em 25 de setembro.
A série ainda não tem data de estreia confirmada no Brasil, mas a expectativa é de que seja lançada no Universal+, serviço de streaming que costuma exibir produções do catálogo da Peacock.
De acordo com a Millennium Media, Noah Centineo foi escolhido para interpretar uma versão mais jovem de John Rambo no filme que contará os primórdios do lendário personagem. A produção, que é simplesmente chamada John Rambo, tenta examinar os primeiros anos do soldado antes dos eventos de First Blood (1982).
Jalmari Helander, diretor finlandês mais conhecido pelo blockbuster de ação Sisu, comandará o filme. Rory Haines e Sohrab Noshirvani, que trabalharam juntos em Black Adam, estão cuidando do roteiro. As filmagens na Tailândia estão previstas para começar no início de 2026.
A história se passará durante a Guerra do Vietnã, embora os detalhes da trama estejam sendo mantidos em sigilo. O objetivo é retratar a metamorfose de John Rambo. Antes da guerra, ele era "o cara perfeito, o mais popular da escola, um superatleta", como Sylvester Stallone afirmou em 2019. Espera-se que o filme examine os eventos horríveis que o moldaram no veterano atormentado retratado no primeiro filme.
Embora não esteja diretamente envolvido no projeto, Sylvester Stallone, que interpretou o personagem em cinco filmes, está ciente dele. Segundo pessoas próximas à produção, ele foi informado sobre a escolha de Centineo. O ator, hoje com 79 anos, brincou em 2023 sobre a possibilidade de voltar a interpretar o papel, dizendo: "Ele já fez praticamente tudo. O que eu vou combater? Artrite?"
A escolha de Centineo, de 29 anos, marca uma nova fase na carreira do ator, que conquistou fama internacional com comédias românticas da Netflix, como a trilogia Para Todos os Garotos que Já Amei. Nos últimos anos, porém, ele vem explorando o gênero de ação, interpretando o herói Esmaga-Átomo em Adão Negro e estrelando a série de espionagem O Recruta. Recentemente, Centineo também esteve no drama de guerra Warfare, da A24, e está escalado para viver Ken Masters no próximo filme de Street Fighter.
A franquia Rambo, baseada no livro First Blood, de David Morrell, é uma das mais conhecidas do cinema de ação. Os cinco filmes arrecadaram mais de 850 milhões de dólares em todo o mundo. Enquanto as sequências apostaram em ação em grande escala, o primeiro longa se destaca pelo tom mais solene e pela crítica ao tratamento dado aos veteranos do Vietnã.
A produção do novo filme está a cargo de Avi Lerner, Jonathan Yunger, Les Weldon e Kevin King-Templeton. A Lionsgate, que distribuiu os dois últimos longas da série, é a principal candidata a adquirir os direitos de distribuição do projeto.
O presidente Donald Trump, agora à frente do conselho do John F. Kennedy Center for the Performing Arts, anunciou pessoalmente os homenageados de 2025 do prestigiado prêmio cultural. Os escolhidos são o ator e cineasta Sylvester Stallone, a banda de rock Kiss, a cantora Gloria Gaynor, o astro da música country George Strait e o ator britânico Michael Crawford, conhecido por seu papel em O Fantasma da Ópera.
A cerimônia, que será a 48ª edição do evento, será transmitida pela CBS e pela plataforma Paramount+ e ocorrerá em Washington, D.C., no dia 7 de dezembro. Em um evento amplamente divulgado, Trump anunciou os homenageados durante uma coletiva de imprensa na quarta-feira, contrariando o costume de divulgar os nomes por comunicado. Ele afirmou ter se envolvido “cerca de 98%” no processo de seleção e que rejeitou alguns nomes por serem “demasiado woke”.
A alteração, na verdade, representa o início de uma nova fase para o Kennedy Center. Depois de reassumir a presidência, Trump trocou os indicados de administrações passadas por novos integrantes comprometidos em reorientar o centro. Ele afirmou que seu objetivo é reverter o que considera ser a "programação política 'woke'" e, em tom de brincadeira, sugeriu que poderia receber uma homenagem no ano seguinte.
Trump se absteve de comparecer a qualquer cerimônia no Kennedy Center durante seu primeiro mandato, depois que vários artistas, incluindo o produtor Norman Lear, ameaçaram boicotar o evento em protesto. Agora, além de supervisionar as escolhas, será o anfitrião da gala.
Os homenageados representam, em certa medida, os interesses pessoais do presidente. Sylvester Stallone, amigo e apoiador de longa data, o descreveu como “um segundo George Washington”. Em janeiro, Trump o nomeou, juntamente com Mel Gibson e Jon Voight, como “embaixador especial” de Hollywood. Michael Crawford foi o protagonista de O Fantasma da Ópera, um dos musicais preferidos do presidente.
A seleção da banda Kiss traz um contexto um pouco mais complicado. Embora Ace Frehley, um dos membros fundadores, tenha apoiado Trump, outros integrantes, como Paul Stanley e Gene Simmons, já expressaram críticas ao presidente em ocasiões anteriores. Simmons, ex-participante do reality show O Aprendiz, declarou em 2022 que Trump "não é republicano nem democrata. Ele está em causa própria."
A nova gestão do Kennedy Center já provocou respostas no cenário artístico. Os organizadores do musical Hamilton cancelaram a apresentação da turnê nacional no local, e outros artistas, como a atriz Issa Rae e a produtora Shonda Rhimes, também romperam relações com a instituição em protesto. Em contrapartida, o anúncio dos homenageados gerou tanto interesse que o site oficial do Kennedy Center ficou temporariamente fora do ar devido ao grande volume de tráfego.
Estabelecido em 1978, o Kennedy Center Honors possui uma tradição bipartidária, congregando presidentes de diversos partidos para homenagear artistas notáveis de todos os gêneros e estilos.