diff --git a/Dockerfile b/Dockerfile index 83df167..4c98250 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,46 +1,50 @@ -# Dockerfile para Monitor Evolution API + Webhook -# Deploy otimizado para Coolify - -FROM python:3.11-slim - -# Definir diretório de trabalho -WORKDIR /app - -# Variáveis de ambiente para Python -ENV PYTHONDONTWRITEBYTECODE=1 -ENV PYTHONUNBUFFERED=1 - -# Variáveis de ambiente para Streamlit -ENV STREAMLIT_SERVER_PORT=3000 -ENV STREAMLIT_SERVER_ADDRESS=0.0.0.0 -ENV STREAMLIT_SERVER_HEADLESS=true -ENV STREAMLIT_BROWSER_GATHER_USAGE_STATS=false - -# Instalar dependências do sistema -RUN apt-get update && apt-get install -y --no-install-recommends \ - curl \ - && rm -rf /var/lib/apt/lists/* - -# Atualizar pip -RUN pip install --no-cache-dir --upgrade pip - -# Copiar arquivos da aplicação -COPY app.py monitor_logic.py ./ - -# Copiar requirements.txt se existir, caso contrário instalar dependências diretamente -COPY requirements.txt* ./ -RUN if [ -f requirements.txt ]; then \ - pip install --no-cache-dir -r requirements.txt; \ - else \ - pip install --no-cache-dir streamlit>=1.28.0 requests>=2.31.0 python-dotenv>=1.0.0 apscheduler>=3.10.0 pandas>=2.0.0; \ - fi - -# Expor porta do Streamlit (padrão Coolify) -EXPOSE 3000 - -# Healthcheck para Coolify -HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ - CMD curl --fail http://localhost:3000/_stcore/health || exit 1 - -# Comando para iniciar a aplicação na porta 3000 -CMD ["streamlit", "run", "app.py", "--server.port=3000", "--server.address=0.0.0.0"] +# Dockerfile para Monitor Evolution API + Webhook +# Deploy otimizado para Coolify + +FROM python:3.11-slim + +# Definir diretório de trabalho +WORKDIR /app + +# Variáveis de ambiente para Python +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONUNBUFFERED=1 + +# Variáveis de ambiente para Streamlit +ENV STREAMLIT_SERVER_PORT=3000 +ENV STREAMLIT_SERVER_ADDRESS=0.0.0.0 +ENV STREAMLIT_SERVER_HEADLESS=true +ENV STREAMLIT_BROWSER_GATHER_USAGE_STATS=false + +# Timezone padrão (pode ser sobrescrito via variável de ambiente no Coolify) +ENV TIMEZONE=America/Sao_Paulo +ENV TZ=America/Sao_Paulo + +# Instalar dependências do sistema +RUN apt-get update && apt-get install -y --no-install-recommends \ + curl \ + && rm -rf /var/lib/apt/lists/* + +# Atualizar pip +RUN pip install --no-cache-dir --upgrade pip + +# Copiar arquivos da aplicação +COPY app.py monitor_logic.py ./ + +# Copiar requirements.txt se existir, caso contrário instalar dependências diretamente +COPY requirements.txt* ./ +RUN if [ -f requirements.txt ]; then \ + pip install --no-cache-dir -r requirements.txt; \ + else \ + pip install --no-cache-dir streamlit>=1.28.0 requests>=2.31.0 python-dotenv>=1.0.0 apscheduler>=3.10.0 pandas>=2.0.0; \ + fi + +# Expor porta do Streamlit (padrão Coolify) +EXPOSE 3000 + +# Healthcheck para Coolify +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD curl --fail http://localhost:3000/_stcore/health || exit 1 + +# Comando para iniciar a aplicação na porta 3000 +CMD ["streamlit", "run", "app.py", "--server.port=3000", "--server.address=0.0.0.0"] diff --git a/app.py b/app.py index 5e01d70..09e3406 100644 --- a/app.py +++ b/app.py @@ -1,190 +1,198 @@ -import streamlit as st -import pandas as pd -import time -from datetime import datetime -from apscheduler.schedulers.background import BackgroundScheduler -from monitor_logic import run_monitor_check, diagnose_n8n_webhook - -# ===== Configuração da Página (DEVE ser a primeira chamada Streamlit!) ===== -st.set_page_config( - page_title="Monitor Evolution API", - page_icon="🤖", - layout="wide" -) - -# Função para gerar tabela HTML customizada -def gerar_tabela_html(logs): - """Gera tabela HTML com estilos inline para garantir funcionamento.""" - if not logs: - return "" - - # Estilos inline - estilo_tabela = "width: 100%; border-collapse: collapse; margin-top: 10px;" - estilo_header = "background-color: #f0f2f6; padding: 12px 15px; text-align: center; font-weight: bold; font-size: 16px; border-bottom: 2px solid #ddd;" - estilo_celula = "padding: 10px 15px; text-align: center; font-size: 14px; border-bottom: 1px solid #eee;" - estilo_celula_detalhes = "padding: 10px 15px; text-align: left; font-size: 14px; border-bottom: 1px solid #eee;" - - html = f'' - - # Cabeçalho - html += '' - html += f'' - html += f'' - html += f'' - html += f'' - html += f'' - html += '' - - # Corpo - html += '' - for log in logs: - html += '' - html += f'' - html += f'' - html += f'' - html += f'' - html += f'' - html += '' - html += '
HoraStatusWhatsAppEmailDetalhes
{log["Hora"]}{log["Status"]}{log["WhatsApp"]}{log["Email"]}{log["Detalhes"]}
' - - return html - -# ===== Inicializar estado da sessão ===== -if 'logs' not in st.session_state: - st.session_state['logs'] = [] - -if 'last_run' not in st.session_state: - st.session_state['last_run'] = "Nunca" - -if 'intervalo_horas' not in st.session_state: - st.session_state['intervalo_horas'] = 3 - -# ===== Função wrapper para o Scheduler gravar logs ===== -def scheduled_job(): - """Executa verificação agendada (roda em background thread).""" - print(f"[{datetime.now()}] Executando verificação agendada...") - status, messages, whatsapp_status, email_status = run_monitor_check() - print(f"Status: {status} | WhatsApp: {whatsapp_status} | Email: {email_status}") - print("\n".join(messages)) - -# ===== Configurar o Agendador (Singleton via cache_resource) ===== -@st.cache_resource -def init_scheduler(intervalo_horas): - scheduler = BackgroundScheduler(timezone="America/Sao_Paulo") - # Job com intervalo configurável a partir de 00:00 - scheduler.add_job( - scheduled_job, - 'interval', - hours=intervalo_horas, - start_date='2025-01-01 00:00:00' - ) - scheduler.start() - return scheduler - -scheduler = init_scheduler(st.session_state['intervalo_horas']) - -# ===== Interface Principal ===== -st.title("🤖 Monitor Evolution API + Webhook") -st.markdown("---") - -# Métricas superiores -col1, col2 = st.columns(2) - -with col1: - st.metric(label="Última Verificação", value=st.session_state['last_run']) - -with col2: - st.write("### Status Agendador") - intervalo_atual = st.session_state['intervalo_horas'] - st.success(f"✅ Ativo (a cada {intervalo_atual}h desde 00:00)") - -st.markdown("### Ações") - -# Botão de execução manual -if st.button("🚀 Executar Verificação Agora", type="primary"): - with st.spinner('Verificando status dos serviços...'): - status, messages, whatsapp_status, email_status = run_monitor_check() - st.session_state['last_run'] = datetime.now().strftime("%H:%M:%S") - - # Adicionar ao histórico de logs - timestamp = datetime.now().strftime("%H:%M:%S") - entry = { - "Hora": timestamp, - "Status": "✅ Sucesso" if status else "❌ Erro", - "WhatsApp": whatsapp_status, - "Email": email_status, - "Detalhes": " | ".join(messages) - } - st.session_state['logs'].insert(0, entry) # Adiciona no topo - - if status: - st.success("✅ Todos os serviços estão operacionais!") - else: - st.error("❌ Falha detectada em um ou mais serviços. Verifique os logs.") - -st.markdown("---") -st.markdown("### 📋 Histórico de Execuções (Sessão Atual)") - -if st.session_state['logs']: - # Renderizar tabela HTML customizada - tabela_html = gerar_tabela_html(st.session_state['logs']) - st.markdown(tabela_html, unsafe_allow_html=True) -else: - st.info("ℹ️ Nenhuma verificação executada nesta sessão ainda.") - -# ===== Sidebar ===== -st.sidebar.title("ℹ️ Sobre") -st.sidebar.info( - """ - Este aplicativo monitora: - - 1. **Evolution API**: Verifica se a instância está online. - 2. **N8N Webhook**: Verifica se o endpoint responde. - - **Notificações:** - - ✅ **Sucesso**: Envia WhatsApp. - - ❌ **Erro**: Envia Email. - """ -) - -st.sidebar.markdown("---") - -# Configuração do Intervalo de Disparo -st.sidebar.markdown("### ⏱️ Intervalo de Disparo") -intervalo_slider = st.sidebar.slider( - "Executar a cada (horas):", - min_value=1, - max_value=12, - value=st.session_state['intervalo_horas'], - step=1, - help="Define o intervalo entre execuções automáticas a partir de 00:00" -) - -# Mostrar horários de execução baseados no intervalo -horarios = [f"{h:02d}:00" for h in range(0, 24, intervalo_slider)] -st.sidebar.caption(f"Horários: {', '.join(horarios)}") - -# Verificar se o intervalo mudou -if intervalo_slider != st.session_state['intervalo_horas']: - st.sidebar.warning("⚠️ Reinicie a aplicação para aplicar o novo intervalo.") - st.session_state['intervalo_horas'] = intervalo_slider - -st.sidebar.markdown("---") - -# Diagnóstico N8N na Sidebar -with st.sidebar.expander("🛠️ Diagnóstico N8N"): - st.write("Teste isolado do Webhook N8N") - if st.button("Testar Webhook N8N", key="btn_diag"): - with st.spinner("Enviando POST de teste..."): - success, log_data = diagnose_n8n_webhook() - - if success: - st.success(f"✅ Status: {log_data['status_code']}") - else: - st.error(f"❌ Erro: {log_data.get('status_code') or log_data.get('error')}") - - st.json(log_data) - -# Informações de configuração -st.sidebar.markdown("---") -st.sidebar.caption(f"Intervalo atual: {st.session_state['intervalo_horas']}h") +import streamlit as st +import pandas as pd +import time +import os +from datetime import datetime +import pytz +from apscheduler.schedulers.background import BackgroundScheduler +from monitor_logic import run_monitor_check, diagnose_n8n_webhook + +# Carregar timezone da variável de ambiente (padrão: America/Sao_Paulo) +TIMEZONE = os.getenv("TIMEZONE", "America/Sao_Paulo") +tz = pytz.timezone(TIMEZONE) + +# ===== Configuração da Página (DEVE ser a primeira chamada Streamlit!) ===== +st.set_page_config( + page_title="Monitor Evolution API", + page_icon="🤖", + layout="wide" +) + +# Função para gerar tabela HTML customizada +def gerar_tabela_html(logs): + """Gera tabela HTML com estilos inline para garantir funcionamento.""" + if not logs: + return "" + + # Estilos inline + estilo_tabela = "width: 100%; border-collapse: collapse; margin-top: 10px;" + estilo_header = "background-color: #f0f2f6; padding: 12px 15px; text-align: center; font-weight: bold; font-size: 16px; border-bottom: 2px solid #ddd;" + estilo_celula = "padding: 10px 15px; text-align: center; font-size: 14px; border-bottom: 1px solid #eee;" + estilo_celula_detalhes = "padding: 10px 15px; text-align: left; font-size: 14px; border-bottom: 1px solid #eee;" + + html = f'' + + # Cabeçalho + html += '' + html += f'' + html += f'' + html += f'' + html += f'' + html += f'' + html += '' + + # Corpo + html += '' + for log in logs: + html += '' + html += f'' + html += f'' + html += f'' + html += f'' + html += f'' + html += '' + html += '
HoraStatusWhatsAppEmailDetalhes
{log["Hora"]}{log["Status"]}{log["WhatsApp"]}{log["Email"]}{log["Detalhes"]}
' + + return html + +# ===== Inicializar estado da sessão ===== +if 'logs' not in st.session_state: + st.session_state['logs'] = [] + +if 'last_run' not in st.session_state: + st.session_state['last_run'] = "Nunca" + +if 'intervalo_horas' not in st.session_state: + st.session_state['intervalo_horas'] = 3 + +# ===== Função wrapper para o Scheduler gravar logs ===== +def scheduled_job(): + """Executa verificação agendada (roda em background thread).""" + now = datetime.now(tz) + print(f"[{now}] Executando verificação agendada...") + status, messages, whatsapp_status, email_status = run_monitor_check() + print(f"Status: {status} | WhatsApp: {whatsapp_status} | Email: {email_status}") + print("\n".join(messages)) + +# ===== Configurar o Agendador (Singleton via cache_resource) ===== +@st.cache_resource +def init_scheduler(intervalo_horas): + scheduler = BackgroundScheduler(timezone=TIMEZONE) + # Job com intervalo configurável a partir de 00:00 + scheduler.add_job( + scheduled_job, + 'interval', + hours=intervalo_horas, + start_date='2025-01-01 00:00:00' + ) + scheduler.start() + return scheduler + +scheduler = init_scheduler(st.session_state['intervalo_horas']) + +# ===== Interface Principal ===== +st.title("🤖 Monitor Evolution API + Webhook") +st.markdown("---") + +# Métricas superiores +col1, col2 = st.columns(2) + +with col1: + st.metric(label="Última Verificação", value=st.session_state['last_run']) + +with col2: + st.write("### Status Agendador") + intervalo_atual = st.session_state['intervalo_horas'] + st.success(f"✅ Ativo (a cada {intervalo_atual}h desde 00:00)") + +st.markdown("### Ações") + +# Botão de execução manual +if st.button("🚀 Executar Verificação Agora", type="primary"): + with st.spinner('Verificando status dos serviços...'): + status, messages, whatsapp_status, email_status = run_monitor_check() + now = datetime.now(tz) + st.session_state['last_run'] = now.strftime("%H:%M:%S") + + # Adicionar ao histórico de logs + timestamp = now.strftime("%H:%M:%S") + entry = { + "Hora": timestamp, + "Status": "✅ Sucesso" if status else "❌ Erro", + "WhatsApp": whatsapp_status, + "Email": email_status, + "Detalhes": " | ".join(messages) + } + st.session_state['logs'].insert(0, entry) # Adiciona no topo + + if status: + st.success("✅ Todos os serviços estão operacionais!") + else: + st.error("❌ Falha detectada em um ou mais serviços. Verifique os logs.") + +st.markdown("---") +st.markdown("### 📋 Histórico de Execuções (Sessão Atual)") + +if st.session_state['logs']: + # Renderizar tabela HTML customizada + tabela_html = gerar_tabela_html(st.session_state['logs']) + st.markdown(tabela_html, unsafe_allow_html=True) +else: + st.info("ℹ️ Nenhuma verificação executada nesta sessão ainda.") + +# ===== Sidebar ===== +st.sidebar.title("ℹ️ Sobre") +st.sidebar.info( + """ + Este aplicativo monitora: + + 1. **Evolution API**: Verifica se a instância está online. + 2. **N8N Webhook**: Verifica se o endpoint responde. + + **Notificações:** + - ✅ **Sucesso**: Envia WhatsApp. + - ❌ **Erro**: Envia Email. + """ +) + +st.sidebar.markdown("---") + +# Configuração do Intervalo de Disparo +st.sidebar.markdown("### ⏱️ Intervalo de Disparo") +intervalo_slider = st.sidebar.slider( + "Executar a cada (horas):", + min_value=1, + max_value=12, + value=st.session_state['intervalo_horas'], + step=1, + help="Define o intervalo entre execuções automáticas a partir de 00:00" +) + +# Mostrar horários de execução baseados no intervalo +horarios = [f"{h:02d}:00" for h in range(0, 24, intervalo_slider)] +st.sidebar.caption(f"Horários: {', '.join(horarios)}") + +# Verificar se o intervalo mudou +if intervalo_slider != st.session_state['intervalo_horas']: + st.sidebar.warning("⚠️ Reinicie a aplicação para aplicar o novo intervalo.") + st.session_state['intervalo_horas'] = intervalo_slider + +st.sidebar.markdown("---") + +# Diagnóstico N8N na Sidebar +with st.sidebar.expander("🛠️ Diagnóstico N8N"): + st.write("Teste isolado do Webhook N8N") + if st.button("Testar Webhook N8N", key="btn_diag"): + with st.spinner("Enviando POST de teste..."): + success, log_data = diagnose_n8n_webhook() + + if success: + st.success(f"✅ Status: {log_data['status_code']}") + else: + st.error(f"❌ Erro: {log_data.get('status_code') or log_data.get('error')}") + + st.json(log_data) + +# Informações de configuração +st.sidebar.markdown("---") +st.sidebar.caption(f"Intervalo atual: {st.session_state['intervalo_horas']}h") diff --git a/monitor_logic.py b/monitor_logic.py index ea4d469..8461fa9 100644 --- a/monitor_logic.py +++ b/monitor_logic.py @@ -1,274 +1,285 @@ -import os -import requests -import smtplib -from email.mime.text import MIMEText -from datetime import datetime -from dotenv import load_dotenv - -# Carregar variáveis de ambiente -load_dotenv() - -# Configurações -EVOLUTION_API_URL = os.getenv("EVOLUTION_API_URL") -EVOLUTION_INSTANCE_ID = os.getenv("EVOLUTION_INSTANCE_ID") -EVOLUTION_API_TOKEN = os.getenv("EVOLUTION_API_TOKEN") -EVOLUTION_SENDER_INSTANCE = os.getenv("EVOLUTION_SENDER_INSTANCE", "Wander") -N8N_WEBHOOK_URL = os.getenv("N8N_WEBHOOK_URL") -WHATSAPP_NUMBER = os.getenv("WHATSAPP_NUMBER") -SMTP_SERVER = os.getenv("SMTP_SERVER", "smtp-mail.outlook.com") -SMTP_PORT = int(os.getenv("SMTP_PORT", 587)) -SMTP_EMAIL = os.getenv("SMTP_EMAIL") -SMTP_PASSWORD = os.getenv("SMTP_PASSWORD") -EMAIL_TO = os.getenv("EMAIL_TO") - -def get_headers(): - return { - "apikey": EVOLUTION_API_TOKEN, - "Authorization": f"Bearer {EVOLUTION_API_TOKEN}", - "Content-Type": "application/json" - } - -def send_email_error(error_details): - """Envia email de erro caso algo falhe.""" - try: - subject = "🚨 ERRO - Monitor Evolution API" - body = f"Falha no monitoramento.\nHorário: {datetime.now().strftime('%d/%m/%Y %H:%M:%S')}\n\nDetalhes do Erro:\n{error_details}" - - msg = MIMEText(body) - msg['Subject'] = subject - msg['From'] = SMTP_EMAIL - msg['To'] = EMAIL_TO - - # Ler configuração de segurança (Default: STARTTLS) - smtp_security = os.getenv("SMTP_SECURITY", "STARTTLS").upper() - - if smtp_security == "SSL": - server = smtplib.SMTP_SSL(SMTP_SERVER, SMTP_PORT) - else: - server = smtplib.SMTP(SMTP_SERVER, SMTP_PORT) - if smtp_security == "STARTTLS": - server.starttls() - - with server: - server.login(SMTP_EMAIL, SMTP_PASSWORD) - server.send_message(msg) - - return True, "Email de erro enviado com sucesso." - except Exception as e: - return False, f"Falha ao enviar email: {str(e)}" - -def send_whatsapp_success(): - """Envia mensagem de sucesso via Evolution API.""" - try: - url = f"{EVOLUTION_API_URL}/message/sendText/{EVOLUTION_SENDER_INSTANCE}" - - now = datetime.now() - message_text = ( - f"✅ *MONITOR OK*\n\n" - f"📅 Data: {now.strftime('%d/%m/%Y')}\n" - f"🕐 Horário: {now.strftime('%H:%M:%S')}\n\n" - f"• Evolution API: Online\n" - f"• Webhook Instância: Habilitado\n" - f"• Webhook N8N: Funcionando\n\n" - f"Todos os serviços operacionais." - ) - - payload = { - "number": WHATSAPP_NUMBER, - "text": message_text - } - - # A Evolution API às vezes aceita apikey no header ou como query param, - # mas o padrão aqui é usar o header configurado. - response = requests.post(url, json=payload, headers=get_headers(), timeout=10) - - if response.status_code == 200 or response.status_code == 201: - return True, "WhatsApp de sucesso enviado." - else: - return False, f"Falha ao enviar WhatsApp: {response.text}" - - except Exception as e: - return False, f"Erro na requisição WhatsApp: {str(e)}" - -def run_monitor_check(): - """Executa o fluxo de verificação completo.""" - log_messages = [] - whatsapp_status = "-" - email_status = "-" - - # Passo 1: Verificar Health Evolution API - try: - # Endpoint correto: /instance/connectionState/{instanceName} - # Usa o nome da instância (EVOLUTION_SENDER_INSTANCE) e não o ID - health_url = f"{EVOLUTION_API_URL}/instance/connectionState/{EVOLUTION_SENDER_INSTANCE}" - resp_health = requests.get(health_url, headers=get_headers(), timeout=10) - - if resp_health.status_code == 200: - # Validar se a instância está realmente conectada (state: open) - try: - data = resp_health.json() - instance_data = data.get('instance', {}) - state = instance_data.get('state') or data.get('state') - - if state == 'open': - log_messages.append("✅ Evolution API Online") - else: - # Estado não é 'open' (ex: close, closed, connecting) = ERRO! - error_msg = f"Evolution API Desconectada (State: {state})" - log_messages.append(f"❌ {error_msg}") - - # Enviar email de erro - email_ok, _ = send_email_error(error_msg) - email_status = "Sucesso" if email_ok else "Erro" - - return False, log_messages, whatsapp_status, email_status - - except Exception as json_err: - # Se falhar ler JSON mas deu 200 OK, verificar se é HTML - raw_text = resp_health.text.strip() - if raw_text.startswith(' Enviar WhatsApp - # Se está tudo OK, email de erro não é enviado, então mantemos email_status como "-" ou podemos por "N/A" - # O usuário pediu "Sucesso ou Error". Se não enviou nada, talvez "-" seja melhor que "Error". - - success, msg = send_whatsapp_success() - if success: - log_messages.append(f"✅ {msg}") - whatsapp_status = "Sucesso" - return True, log_messages, whatsapp_status, email_status - else: - # Se falhar enviar o whats, é um erro também? - whatsapp_status = "Erro" - - # O fluxo original manda email de erro se o check falhar. - # Se o envio do whats falhar, vamos mandar email - error_msg = f"Serviços Online, mas falha ao enviar WhatsApp: {msg}" - log_messages.append(f"⚠️ {error_msg}") - - email_ok, _ = send_email_error(error_msg) - email_status = "Sucesso" if email_ok else "Erro" - - return False, log_messages, whatsapp_status, email_status - -def diagnose_n8n_webhook(): - """Realiza um teste detalhado do Webhook N8N para diagnóstico.""" - try: - url = N8N_WEBHOOK_URL - payload = {"check": "diagnosis_ping", "timestamp": str(datetime.now())} - - # Preparar log - log = { - "url": url, - "method": "POST", - "payload_sent": payload, - "status_code": None, - "response_body": None, - "headers_received": None, - "error": None - } - - response = requests.post(url, json=payload, timeout=10) - - log["status_code"] = response.status_code - log["response_body"] = response.text - log["headers_received"] = dict(response.headers) - - if response.status_code == 200: - return True, log - else: - return False, log - - except Exception as e: - log["error"] = str(e) - return False, log - -if __name__ == "__main__": - # Teste rápido se rodar direto - status, logs, wa_status, em_status = run_monitor_check() - print(f"Status: {status}") - print(f"WhatsApp: {wa_status} | Email: {em_status}") - print("\n".join(logs)) +import os +import requests +import smtplib +from email.mime.text import MIMEText +from datetime import datetime +import pytz +from dotenv import load_dotenv + +# Carregar variáveis de ambiente +load_dotenv() + +# Configurações +EVOLUTION_API_URL = os.getenv("EVOLUTION_API_URL") +EVOLUTION_INSTANCE_ID = os.getenv("EVOLUTION_INSTANCE_ID") +EVOLUTION_API_TOKEN = os.getenv("EVOLUTION_API_TOKEN") +EVOLUTION_SENDER_INSTANCE = os.getenv("EVOLUTION_SENDER_INSTANCE", "Wander") +N8N_WEBHOOK_URL = os.getenv("N8N_WEBHOOK_URL") +WHATSAPP_NUMBER = os.getenv("WHATSAPP_NUMBER") +SMTP_SERVER = os.getenv("SMTP_SERVER", "smtp-mail.outlook.com") +SMTP_PORT = int(os.getenv("SMTP_PORT", 587)) +SMTP_EMAIL = os.getenv("SMTP_EMAIL") +SMTP_PASSWORD = os.getenv("SMTP_PASSWORD") +EMAIL_TO = os.getenv("EMAIL_TO") + +# Timezone configurável via variável de ambiente +TIMEZONE = os.getenv("TIMEZONE", "America/Sao_Paulo") +tz = pytz.timezone(TIMEZONE) + +def get_now(): + """Retorna datetime atual com timezone configurado.""" + return datetime.now(tz) + +def get_headers(): + return { + "apikey": EVOLUTION_API_TOKEN, + "Authorization": f"Bearer {EVOLUTION_API_TOKEN}", + "Content-Type": "application/json" + } + +def send_email_error(error_details): + """Envia email de erro caso algo falhe.""" + try: + now = get_now() + subject = "🚨 ERRO - Monitor Evolution API" + body = f"Falha no monitoramento.\nHorário: {now.strftime('%d/%m/%Y %H:%M:%S')}\n\nDetalhes do Erro:\n{error_details}" + + msg = MIMEText(body) + msg['Subject'] = subject + msg['From'] = SMTP_EMAIL + msg['To'] = EMAIL_TO + + # Ler configuração de segurança (Default: STARTTLS) + smtp_security = os.getenv("SMTP_SECURITY", "STARTTLS").upper() + + if smtp_security == "SSL": + server = smtplib.SMTP_SSL(SMTP_SERVER, SMTP_PORT) + else: + server = smtplib.SMTP(SMTP_SERVER, SMTP_PORT) + if smtp_security == "STARTTLS": + server.starttls() + + with server: + server.login(SMTP_EMAIL, SMTP_PASSWORD) + server.send_message(msg) + + return True, "Email de erro enviado com sucesso." + except Exception as e: + return False, f"Falha ao enviar email: {str(e)}" + +def send_whatsapp_success(): + """Envia mensagem de sucesso via Evolution API.""" + try: + url = f"{EVOLUTION_API_URL}/message/sendText/{EVOLUTION_SENDER_INSTANCE}" + + now = get_now() + message_text = ( + f"✅ *MONITOR OK*\n\n" + f"📅 Data: {now.strftime('%d/%m/%Y')}\n" + f"🕐 Horário: {now.strftime('%H:%M:%S')}\n\n" + f"• Evolution API: Online\n" + f"• Webhook Instância: Habilitado\n" + f"• Webhook N8N: Funcionando\n\n" + f"Todos os serviços operacionais." + ) + + payload = { + "number": WHATSAPP_NUMBER, + "text": message_text + } + + # A Evolution API às vezes aceita apikey no header ou como query param, + # mas o padrão aqui é usar o header configurado. + response = requests.post(url, json=payload, headers=get_headers(), timeout=10) + + if response.status_code == 200 or response.status_code == 201: + return True, "WhatsApp de sucesso enviado." + else: + return False, f"Falha ao enviar WhatsApp: {response.text}" + + except Exception as e: + return False, f"Erro na requisição WhatsApp: {str(e)}" + +def run_monitor_check(): + """Executa o fluxo de verificação completo.""" + log_messages = [] + whatsapp_status = "-" + email_status = "-" + + # Passo 1: Verificar Health Evolution API + try: + # Endpoint correto: /instance/connectionState/{instanceName} + # Usa o nome da instância (EVOLUTION_SENDER_INSTANCE) e não o ID + health_url = f"{EVOLUTION_API_URL}/instance/connectionState/{EVOLUTION_SENDER_INSTANCE}" + resp_health = requests.get(health_url, headers=get_headers(), timeout=10) + + if resp_health.status_code == 200: + # Validar se a instância está realmente conectada (state: open) + try: + data = resp_health.json() + instance_data = data.get('instance', {}) + state = instance_data.get('state') or data.get('state') + + if state == 'open': + log_messages.append("✅ Evolution API Online") + else: + # Estado não é 'open' (ex: close, closed, connecting) = ERRO! + error_msg = f"Evolution API Desconectada (State: {state})" + log_messages.append(f"❌ {error_msg}") + + # Enviar email de erro + email_ok, _ = send_email_error(error_msg) + email_status = "Sucesso" if email_ok else "Erro" + + return False, log_messages, whatsapp_status, email_status + + except Exception as json_err: + # Se falhar ler JSON mas deu 200 OK, verificar se é HTML + raw_text = resp_health.text.strip() + if raw_text.startswith(' Enviar WhatsApp + # Se está tudo OK, email de erro não é enviado, então mantemos email_status como "-" ou podemos por "N/A" + # O usuário pediu "Sucesso ou Error". Se não enviou nada, talvez "-" seja melhor que "Error". + + success, msg = send_whatsapp_success() + if success: + log_messages.append(f"✅ {msg}") + whatsapp_status = "Sucesso" + return True, log_messages, whatsapp_status, email_status + else: + # Se falhar enviar o whats, é um erro também? + whatsapp_status = "Erro" + + # O fluxo original manda email de erro se o check falhar. + # Se o envio do whats falhar, vamos mandar email + error_msg = f"Serviços Online, mas falha ao enviar WhatsApp: {msg}" + log_messages.append(f"⚠️ {error_msg}") + + email_ok, _ = send_email_error(error_msg) + email_status = "Sucesso" if email_ok else "Erro" + + return False, log_messages, whatsapp_status, email_status + +def diagnose_n8n_webhook(): + """Realiza um teste detalhado do Webhook N8N para diagnóstico.""" + try: + url = N8N_WEBHOOK_URL + now = get_now() + payload = {"check": "diagnosis_ping", "timestamp": str(now)} + + # Preparar log + log = { + "url": url, + "method": "POST", + "payload_sent": payload, + "status_code": None, + "response_body": None, + "headers_received": None, + "error": None + } + + response = requests.post(url, json=payload, timeout=10) + + log["status_code"] = response.status_code + log["response_body"] = response.text + log["headers_received"] = dict(response.headers) + + if response.status_code == 200: + return True, log + else: + return False, log + + except Exception as e: + log["error"] = str(e) + return False, log + +if __name__ == "__main__": + # Teste rápido se rodar direto + status, logs, wa_status, em_status = run_monitor_check() + print(f"Status: {status}") + print(f"WhatsApp: {wa_status} | Email: {em_status}") + print("\n".join(logs)) diff --git a/requirements.txt b/requirements.txt index a1eaff3..3194741 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ -streamlit>=1.28.0 -requests>=2.31.0 -python-dotenv>=1.0.0 -apscheduler>=3.10.0 -pandas>=2.0.0 +streamlit>=1.28.0 +requests>=2.31.0 +python-dotenv>=1.0.0 +apscheduler>=3.10.0 +pandas>=2.0.0 +pytz>=2023.3