├── .env.example ├── requirements.txt ├── vercel.json ├── .gitignore ├── teste_email.py ├── database.py ├── TROUBLESHOOTING.md ├── teste-integracao.html ├── app.py └── README.md /.env.example: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/biancaalvess/Notifica-Site/HEAD/.env.example -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | flask 2 | flask-cors 3 | python-dotenv 4 | gunicorn==21.2.0 5 | user-agents 6 | pytz -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "builds": [ 4 | { 5 | "src": "app.py", 6 | "use": "@vercel/python" 7 | } 8 | ], 9 | "routes": [ 10 | { 11 | "src": "/(.*)", 12 | "dest": "app.py" 13 | } 14 | ], 15 | "crons": [ 16 | { 17 | "path": "/enviar-relatorio-diario", 18 | "schedule": "0 20 * * *" 19 | } 20 | ] 21 | } 22 | 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Environment variables 2 | .env 3 | !.env.example 4 | 5 | # Python 6 | __pycache__/ 7 | *.py[cod] 8 | *$py.class 9 | *.so 10 | .Python 11 | env/ 12 | venv/ 13 | ENV/ 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | 30 | # IDE 31 | .vscode/ 32 | .idea/ 33 | *.swp 34 | *.swo 35 | *~ 36 | 37 | # OS 38 | .DS_Store 39 | Thumbs.db 40 | 41 | # Logs 42 | *.log 43 | 44 | # Arquivos de persistência (dados da API) 45 | visitas.json 46 | ultimo_email.json 47 | 48 | -------------------------------------------------------------------------------- /teste_email.py: -------------------------------------------------------------------------------- 1 | from dotenv import load_dotenv 2 | import smtplib 3 | from email.mime.text import MIMEText 4 | from datetime import datetime 5 | import os 6 | 7 | # Carrega variáveis de ambiente 8 | load_dotenv() 9 | 10 | EMAIL_ADDRESS = os.getenv('EMAIL_ADDRESS') 11 | EMAIL_PASSWORD = os.getenv('EMAIL_PASSWORD') 12 | SMTP_SERVER = os.getenv('SMTP_SERVER', 'smtp.gmail.com') 13 | SMTP_PORT = int(os.getenv('SMTP_PORT', 587)) 14 | 15 | print("Testando envio de e-mail...") 16 | print(f"De: {EMAIL_ADDRESS}") 17 | print(f"Para: {EMAIL_ADDRESS}") 18 | print(f"SMTP: {SMTP_SERVER}:{SMTP_PORT}") 19 | 20 | conteudo_html = f""" 21 | 22 | 23 |

Teste de Email

24 |

Este e um email de teste da API de notificacoes.

25 |

Data e Hora: {datetime.now().strftime('%d/%m/%Y %H:%M:%S')}

26 |

Se voce recebeu este email, esta funcionando!

27 | 28 | 29 | """ 30 | 31 | msg = MIMEText(conteudo_html, 'html') 32 | msg['Subject'] = 'Teste de Email - API Notificacao' 33 | msg['From'] = EMAIL_ADDRESS 34 | msg['To'] = EMAIL_ADDRESS 35 | 36 | try: 37 | print("\nTentando conectar ao servidor SMTP...") 38 | with smtplib.SMTP(SMTP_SERVER, SMTP_PORT) as servidor: 39 | print("Iniciando TLS...") 40 | servidor.starttls() 41 | print("Fazendo login...") 42 | servidor.login(EMAIL_ADDRESS, EMAIL_PASSWORD) 43 | print("Enviando email...") 44 | servidor.send_message(msg) 45 | print("\nSUCESSO! Email enviado com sucesso!") 46 | print("Verifique sua caixa de entrada.") 47 | except smtplib.SMTPAuthenticationError as e: 48 | print(f"\nERRO: Falha na autenticacao: {e}") 49 | print("\nPossiveis causas:") 50 | print("1. Email ou senha incorretos") 51 | print("2. Senha de aplicativo nao configurada") 52 | print("3. Verificacao de duas etapas desabilitada") 53 | except Exception as e: 54 | print(f"\nERRO: {e}") 55 | print("\nPossiveis causas:") 56 | print("1. Problemas de conexao") 57 | print("2. Credenciais incorretas") 58 | print("3. Firewall bloqueando a conexao") 59 | 60 | -------------------------------------------------------------------------------- /database.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | from datetime import datetime 4 | import threading 5 | 6 | # Configuração dos arquivos 7 | # O uso de /tmp é necessário para ambientes serverless como Vercel (temporário) 8 | VISITAS_FILE = '/tmp/visitas.json' if os.path.exists('/tmp') else 'visitas.json' 9 | ULTIMO_EMAIL_FILE = '/tmp/ultimo_email.json' if os.path.exists('/tmp') else 'ultimo_email.json' 10 | 11 | # Lock para evitar erros de escrita simultânea 12 | bloqueio = threading.Lock() 13 | 14 | def carregar_visitas(): 15 | """Carrega todas as visitas salvas no arquivo JSON.""" 16 | try: 17 | if os.path.exists(VISITAS_FILE): 18 | with open(VISITAS_FILE, 'r', encoding='utf-8') as f: 19 | dados = json.load(f) 20 | visitas = [] 21 | for v in dados: 22 | try: 23 | # Converte string ISO de volta para datetime 24 | if isinstance(v['tempo'], str): 25 | v['tempo'] = datetime.fromisoformat(v['tempo'].replace('Z', '+00:00')) 26 | visitas.append(v) 27 | except: 28 | continue 29 | return visitas 30 | except Exception as e: 31 | print(f"Erro ao carregar visitas: {e}") 32 | return [] 33 | 34 | def salvar_visita(visita): 35 | """Adiciona uma única visita e salva no arquivo.""" 36 | with bloqueio: 37 | visitas = carregar_visitas() 38 | 39 | # Garante que o tempo esteja em formato serializável (ISO string) 40 | visita_copy = visita.copy() 41 | if isinstance(visita_copy['tempo'], datetime): 42 | visita_copy['tempo'] = visita_copy['tempo'].isoformat() 43 | 44 | # Adiciona à lista existente 45 | visitas_salvar = [] 46 | 47 | # Prepara a lista atual para salvar (converte datetimes para string) 48 | for v in visitas: 49 | v_save = v.copy() 50 | if isinstance(v_save['tempo'], datetime): 51 | v_save['tempo'] = v_save['tempo'].isoformat() 52 | visitas_salvar.append(v_save) 53 | 54 | visitas_salvar.append(visita_copy) 55 | 56 | _escrever_arquivo(visitas_salvar) 57 | 58 | def _escrever_arquivo(dados): 59 | """Função interna para escrever no JSON.""" 60 | try: 61 | with open(VISITAS_FILE, 'w', encoding='utf-8') as f: 62 | json.dump(dados, f, ensure_ascii=False, indent=2) 63 | except Exception as e: 64 | print(f"Erro ao salvar arquivo: {e}") 65 | 66 | def obter_ultimo_email_enviado(): 67 | """Recupera a data do último envio de e-mail.""" 68 | try: 69 | if os.path.exists(ULTIMO_EMAIL_FILE): 70 | with open(ULTIMO_EMAIL_FILE, 'r', encoding='utf-8') as f: 71 | dados = json.load(f) 72 | if dados and 'timestamp' in dados: 73 | return datetime.fromisoformat(dados['timestamp'].replace('Z', '+00:00')) 74 | except Exception as e: 75 | print(f"Erro ao carregar último email: {e}") 76 | return None 77 | 78 | def salvar_ultimo_email_enviado(timestamp): 79 | """Salva a data do envio de e-mail atual.""" 80 | try: 81 | dados = {'timestamp': timestamp.isoformat()} 82 | with open(ULTIMO_EMAIL_FILE, 'w', encoding='utf-8') as f: 83 | json.dump(dados, f) 84 | except Exception as e: 85 | print(f"Erro ao salvar último email: {e}") -------------------------------------------------------------------------------- /TROUBLESHOOTING.md: -------------------------------------------------------------------------------- 1 | # 🔧 Troubleshooting - API não está funcionando 2 | 3 | ## ✅ Arquitetura Atualizada para Vercel (Serverless) 4 | 5 | A API foi refatorada para funcionar corretamente no ambiente serverless do Vercel: 6 | 7 | - **Persistência**: Dados salvos em arquivos JSON (funciona no Vercel) 8 | - **Cron Jobs**: Relatório diário via Vercel Cron (configurado em `vercel.json`) 9 | - **Anti-duplicatas**: Usa persistência em arquivo (funciona com múltiplas instâncias) 10 | 11 | ### Relatório Diário 12 | 13 | O relatório diário agora é enviado via **Cron Job do Vercel** às 17:00 (horário de Brasília). 14 | 15 | - Rota: `/enviar-relatorio-diario` 16 | - Configurado em: `vercel.json` → `crons` 17 | - Horário: 20:00 UTC (17:00 Brasília) 18 | 19 | ## Problemas Comuns e Soluções 20 | 21 | ### 1. API não responde no Vercel 22 | 23 | **Sintomas**: Erro 404 ou timeout ao acessar a API 24 | 25 | **Soluções**: 26 | - Verifique se o arquivo `vercel.json` existe e está configurado 27 | - Certifique-se que as rotas estão mapeadas corretamente 28 | - Verifique os logs no painel do Vercel 29 | 30 | ### 2. E-mails não estão sendo enviados 31 | 32 | **Sintomas**: API responde mas não recebe e-mails 33 | 34 | **Verificações**: 35 | 1. **Variáveis de ambiente no Vercel**: 36 | - Vá em Settings → Environment Variables 37 | - Verifique se estão configuradas: 38 | - `EMAIL_ADDRESS` 39 | - `EMAIL_PASSWORD` 40 | - `SMTP_SERVER` 41 | - `SMTP_PORT` 42 | 43 | 2. **Verificar logs do Vercel**: 44 | - Acesse o painel do Vercel 45 | - Vá em Deployments → [seu deployment] → Logs 46 | - Procure por mensagens como: 47 | - `[SUCESSO] NOTIFICACAO ENVIADA` 48 | - `[ERRO] AUTENTICACAO FALHOU` 49 | - `DEBUG: Função enviar_notificacao_imediata chamada` 50 | 51 | 3. **Testar SMTP**: 52 | ```bash 53 | python teste_email.py 54 | ``` 55 | 56 | ### 3. Erro "Module not found" 57 | 58 | **Sintomas**: Erro ao fazer deploy no Vercel 59 | 60 | **Solução**: 61 | - Verifique se `requirements.txt` está completo 62 | - Certifique-se que todas as dependências estão listadas 63 | 64 | ### 4. Threads não funcionam no Vercel 65 | 66 | **Problema**: O Vercel é serverless e pode ter limitações com threads 67 | 68 | **Solução**: 69 | - O código já está adaptado para funcionar sem threads persistentes 70 | - As threads são criadas apenas para envio de email (daemon threads) 71 | - O schedule pode não funcionar bem no Vercel (funcionalidade serverless) 72 | 73 | ### 5. Timeout no Vercel 74 | 75 | **Sintomas**: Requisições demoram muito ou dão timeout 76 | 77 | **Soluções**: 78 | - Aumente o timeout no `vercel.json` (se necessário) 79 | - Verifique se o SMTP não está demorando muito 80 | - Considere usar async/await para operações de rede 81 | 82 | ### 6. Variáveis de ambiente não estão sendo carregadas 83 | 84 | **Sintomas**: API não encontra EMAIL_ADDRESS ou EMAIL_PASSWORD 85 | 86 | **Solução**: 87 | 1. No Vercel: 88 | - Settings → Environment Variables 89 | - Adicione todas as variáveis 90 | - Certifique-se de fazer redeploy após adicionar 91 | 92 | 2. Verifique no código: 93 | ```python 94 | print(f"Email configurado: {EMAIL_ADDRESS}") 95 | ``` 96 | 97 | ## Teste Local vs Vercel 98 | 99 | ### Teste Local 100 | ```bash 101 | python app.py 102 | # Acesse: http://localhost:5000/track-visit 103 | ``` 104 | 105 | ### Teste Vercel 106 | ```bash 107 | # Testar tracking 108 | curl -X POST https://sua-api.vercel.app/track-visit 109 | 110 | # Testar health check 111 | curl https://sua-api.vercel.app/health 112 | 113 | # Testar relatório diário manualmente 114 | curl https://sua-api.vercel.app/enviar-relatorio-diario 115 | ``` 116 | 117 | ## Verificar Logs 118 | 119 | ### Logs Locais 120 | - Execute `python app.py` e veja o console 121 | 122 | ### Logs Vercel 123 | 1. Acesse: https://vercel.com/dashboard 124 | 2. Selecione seu projeto 125 | 3. Vá em Deployments 126 | 4. Clique no deployment mais recente 127 | 5. Veja a aba "Logs" 128 | 129 | ## Checklist de Debugging 130 | 131 | - [ ] API responde com 200 OK? 132 | - [ ] Variáveis de ambiente configuradas no Vercel? 133 | - [ ] Logs mostram "Email configurado: ..."? 134 | - [ ] Logs mostram "DEBUG: Função enviar_notificacao_imediata chamada"? 135 | - [ ] Logs mostram "[SUCESSO]" ou "[ERRO]"? 136 | - [ ] Teste local funciona? 137 | - [ ] `vercel.json` existe e está correto (incluindo cron)? 138 | - [ ] `requirements.txt` está completo (sem schedule)? 139 | - [ ] Rota `/health` retorna status ok? 140 | - [ ] Rota `/enviar-relatorio-diario` funciona manualmente? 141 | - [ ] Cron job está configurado no Vercel? 142 | 143 | ## Próximos Passos 144 | 145 | 1. Verifique os logs do Vercel 146 | 2. Teste a API localmente 147 | 3. Compare o comportamento local vs Vercel 148 | 4. Verifique variáveis de ambiente 149 | 5. Teste o envio de email isoladamente 150 | 151 | ## Suporte 152 | 153 | Se o problema persistir: 154 | 1. Verifique os logs completos do Vercel 155 | 2. Teste localmente para isolar o problema 156 | 3. Compare configurações entre local e Vercel 157 | 158 | -------------------------------------------------------------------------------- /teste-integracao.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Teste de Integração - API de Notificações 7 | 68 | 69 | 70 |
71 |

🔔 Teste de Integração

72 |

Esta página simula uma visita ao site devbianca.tech

73 |

A API foi chamada automaticamente!

74 | 75 |
76 |

Carregando...

77 |
78 | 79 | 82 |
83 | 84 | 142 | 143 | 144 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, request 2 | from dotenv import load_dotenv 3 | import smtplib 4 | from email.mime.text import MIMEText 5 | from datetime import datetime 6 | import os 7 | from flask_cors import CORS 8 | import threading 9 | from user_agents import parse 10 | import pytz 11 | import database 12 | 13 | app = Flask(__name__) 14 | CORS(app) 15 | 16 | # Carrega variáveis de ambiente 17 | load_dotenv() 18 | EMAIL_ADDRESS = os.getenv('EMAIL_ADDRESS') 19 | EMAIL_PASSWORD = os.getenv('EMAIL_PASSWORD') 20 | SMTP_SERVER = os.getenv('SMTP_SERVER', 'smtp.gmail.com') 21 | SMTP_PORT = int(os.getenv('SMTP_PORT', 587)) 22 | 23 | if not EMAIL_ADDRESS or not EMAIL_PASSWORD: 24 | print("AVISO: EMAIL NAO CONFIGURADO! Verifique o .env") 25 | 26 | def agora_brasilia(): 27 | brasilia_tz = pytz.timezone('America/Sao_Paulo') 28 | return datetime.now(brasilia_tz) 29 | 30 | def eh_bot(user_agent_string): 31 | if not user_agent_string: 32 | return True 33 | user_agent = parse(user_agent_string) 34 | return user_agent.is_bot or 'bot' in user_agent_string.lower() 35 | 36 | def enviar_notificacao_imediata(ip, user_agent): 37 | agora = agora_brasilia() 38 | ultimo_email_enviado = database.obter_ultimo_email_enviado() 39 | 40 | if ultimo_email_enviado: 41 | if ultimo_email_enviado.tzinfo is None: 42 | brasilia_tz = pytz.timezone('America/Sao_Paulo') 43 | ultimo_email_enviado = brasilia_tz.localize(ultimo_email_enviado) 44 | 45 | tempo_desde_ultimo = (agora - ultimo_email_enviado).total_seconds() 46 | if tempo_desde_ultimo < 30: 47 | print(f"DEBUG: Email duplicado ignorado ({tempo_desde_ultimo:.1f}s)") 48 | return False 49 | 50 | if not EMAIL_ADDRESS or not EMAIL_PASSWORD: 51 | return False 52 | 53 | conteudo_html = f""" 54 | 55 | 56 |

Nós temos visita Bia!

57 |

Vamos torcer por uma entrevista, boa sorte!! 🎉

58 | Celebracao GIF 59 |

Data e Hora: {agora.strftime('%d/%m/%Y %H:%M:%S')} (Horário de Brasília)

60 | 61 | 62 | """ 63 | 64 | msg = MIMEText(conteudo_html, 'html') 65 | msg['Subject'] = f'Nova Visita em devbianca.tech - {agora.strftime("%H:%M")}' 66 | msg['From'] = EMAIL_ADDRESS 67 | msg['To'] = EMAIL_ADDRESS 68 | 69 | try: 70 | with smtplib.SMTP(SMTP_SERVER, SMTP_PORT) as servidor: 71 | servidor.starttls() 72 | servidor.login(EMAIL_ADDRESS, EMAIL_PASSWORD) 73 | servidor.send_message(msg) 74 | print(f"[SUCESSO] Notificação enviada em {agora.strftime('%d/%m/%Y %H:%M:%S')}") 75 | database.salvar_ultimo_email_enviado(agora) 76 | return True 77 | except Exception as e: 78 | print(f"[ERRO] Falha ao enviar email: {e}") 79 | return False 80 | 81 | def enviar_relatorio_diario(): 82 | if not EMAIL_ADDRESS or not EMAIL_PASSWORD: 83 | return {"error": "Email não configurado"}, 500 84 | 85 | visitas = database.carregar_visitas() 86 | 87 | if not visitas: 88 | return {"message": "Nenhuma visita registrada no total", "total": 0}, 200 89 | 90 | agora = agora_brasilia() 91 | 92 | visitas_hoje = [] 93 | for visita in visitas: 94 | try: 95 | tempo_visita = visita['tempo'] 96 | if isinstance(tempo_visita, str): 97 | tempo_visita = datetime.fromisoformat(tempo_visita.replace('Z', '+00:00')) 98 | 99 | if tempo_visita.date() == agora.date(): 100 | visitas_hoje.append(visita) 101 | except: 102 | continue 103 | 104 | total_geral = len(visitas) 105 | total_hoje = len(visitas_hoje) 106 | 107 | detalhes_visitas_hoje = "" 108 | if visitas_hoje: 109 | for visita in visitas_hoje: 110 | tempo = visita['tempo'] 111 | if isinstance(tempo, str): 112 | tempo = datetime.fromisoformat(tempo.replace('Z', '+00:00')) 113 | detalhes_visitas_hoje += f"
  • {tempo.strftime('%H:%M:%S')}
  • " 114 | else: 115 | detalhes_visitas_hoje = "
  • Nenhuma visita hoje (até agora).
  • " 116 | 117 | conteudo_html = f""" 118 | 119 | 120 |

    Relatório Diário de Visitas

    121 |

    Data: {agora.strftime('%d/%m/%Y')}

    122 |
    123 |

    Resumo

    124 | 128 |

    Vamos torcer por uma entrevista!!

    129 | 130 |

    Detalhes das Visitas de Hoje:

    131 | 134 | 135 | 136 | """ 137 | 138 | msg = MIMEText(conteudo_html, 'html') 139 | msg['Subject'] = f'Relatório Diário - Total: {total_geral} visitas' 140 | msg['From'] = EMAIL_ADDRESS 141 | msg['To'] = EMAIL_ADDRESS 142 | 143 | try: 144 | with smtplib.SMTP(SMTP_SERVER, SMTP_PORT) as servidor: 145 | servidor.starttls() 146 | servidor.login(EMAIL_ADDRESS, EMAIL_PASSWORD) 147 | servidor.send_message(msg) 148 | 149 | print(f"Relatório enviado. Total acumulado: {total_geral}") 150 | return {"message": "Relatório enviado", "total_geral": total_geral, "hoje": total_hoje}, 200 151 | except Exception as e: 152 | print(f"Erro ao enviar relatório: {e}") 153 | return {"error": str(e)}, 500 154 | 155 | @app.route('/health') 156 | def health(): 157 | return { 158 | "status": "ok", 159 | "total_visitas": len(database.carregar_visitas()), 160 | "timestamp": agora_brasilia().strftime('%d/%m/%Y %H:%M:%S') 161 | } 162 | 163 | @app.route('/enviar-relatorio-diario', methods=['GET', 'POST']) 164 | def rota_enviar_relatorio(): 165 | resultado, status = enviar_relatorio_diario() 166 | return resultado, status 167 | 168 | @app.route('/') 169 | def home(): 170 | user_agent = request.headers.get('User-Agent') 171 | if eh_bot(user_agent): 172 | return "Acesso negado: Bots não são permitidos.", 403 173 | 174 | ip = request.remote_addr 175 | 176 | database.salvar_visita({ 177 | 'tempo': agora_brasilia(), 178 | 'ip': ip, 179 | 'user_agent': user_agent 180 | }) 181 | 182 | threading.Thread(target=enviar_notificacao_imediata, args=(ip, user_agent), daemon=True).start() 183 | return "Bem-vindo ao NotificaSite!" 184 | 185 | # MUDANÇA AQUI: Rota renomeada para evitar AdBlock 186 | @app.route('/api/ping', methods=['GET', 'POST', 'OPTIONS']) 187 | def track_visit(): 188 | user_agent = request.headers.get('User-Agent') 189 | if eh_bot(user_agent): 190 | return {"error": "Bots não são permitidos"}, 403 191 | 192 | ip = request.remote_addr 193 | 194 | database.salvar_visita({ 195 | 'tempo': agora_brasilia(), 196 | 'ip': ip, 197 | 'user_agent': user_agent 198 | }) 199 | 200 | threading.Thread(target=enviar_notificacao_imediata, args=(ip, user_agent), daemon=True).start() 201 | return {"status": "ok", "message": "Visita registrada"} 202 | 203 | if __name__ == '__main__': 204 | app.run(debug=True) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # API de Notificação de Visitas 2 | 3 | Este projeto é uma API simples desenvolvida com Flask (Python) que detecta acessos em sites, armazena um histórico persistente de visitas e envia notificações por e-mail. 4 | 5 | ## O que ela faz? 6 | 7 | 1. **Notificação Imediata**: Envia um e-mail instantâneo a cada nova visita recebida no teu site. 8 | 2. **Relatório Diário**: Envia um resumo automático às 17h00 com: 9 | * Total de visitas do dia. 10 | * **Total Geral Acumulado** (histórico completo desde o início). 11 | * Horários detalhados dos acessos. 12 | 3. **Persistência de Dados**: Mantém o histórico de visitas salvo em arquivo JSON, garantindo que a contagem não zere diariamente. 13 | 4. **Anti-AdBlock**: Utiliza rotas neutras (`/api/ping`) para evitar bloqueio por extensões de privacidade e navegadores como Brave. 14 | 15 | ## 🔗 Endpoints 16 | 17 | * **GET /**: Rota de boas-vindas. 18 | * **POST /api/ping**: Rota para registrar uma visita (Substitui a antiga `/track-visit` para evitar bloqueios de AdBlock). 19 | * **GET /health**: Verifica o status da API e exibe o total de visitas registradas. 20 | * **GET /enviar-relatorio-diario**: Rota acionada pelo Cron Job para enviar o resumo do dia. 21 | 22 | ## 💻 Como integrar no Frontend 23 | 24 | Para registrar uma visita no teu site (React, Next.js, HTML puro, etc.), faz uma requisição `POST` para a rota `/api/ping`: 25 | 26 | ```javascript 27 | // Exemplo de integração 28 | fetch('[https://sua-api.vercel.app/api/ping](https://sua-api.vercel.app/api/ping)', { 29 | method: 'POST', 30 | headers: { 31 | 'Content-Type': 'application/json' 32 | }, 33 | // Opcional: enviar dados extras no corpo se necessário 34 | body: JSON.stringify({}) 35 | }) 36 | .then(response => { 37 | if (response.ok) console.log("Visita registrada!"); 38 | }) 39 | .catch(err => console.error("Erro ao registrar visita", err)); 40 | ```` 41 | 42 | ## 🛠 Tecnologias Utilizadas 43 | 44 | * **Python 3 & Flask**: Backend serverless leve. 45 | * **JSON Database**: Sistema de persistência de dados em arquivo (`database.py`). 46 | * **SMTP (Gmail)**: Para envio seguro de notificações. 47 | * **Vercel Cron**: Para agendamento automático do relatório diário. 48 | * **User-Agents**: Biblioteca para detecção e bloqueio de bots. 49 | 50 | ## ⚙️ Configuração Local 51 | 52 | 1. Clone o repositório: 53 | 54 | ```bash 55 | git clone [https://github.com/seu-usuario/seu-repo.git](https://github.com/seu-usuario/seu-repo.git) 56 | cd seu-repo 57 | ``` 58 | 59 | 2. Crie um ambiente virtual e instale as dependências: 60 | 61 | ```bash 62 | python -m venv venv 63 | source venv/bin/activate # No Windows: venv\Scripts\activate 64 | pip install -r requirements.txt 65 | ``` 66 | 67 | 3. Configure as variáveis de ambiente: 68 | Crie um arquivo `.env` na raiz (baseado no `.env.example`) e adicione: 69 | 70 | ```env 71 | EMAIL_ADDRESS=seuemail@gmail.com 72 | EMAIL_PASSWORD=sua_senha_de_aplicativo 73 | SMTP_SERVER=smtp.gmail.com 74 | SMTP_PORT=587 75 | ``` 76 | 77 | 4. Execute o servidor: 78 | 79 | ```bash 80 | python app.py 81 | ``` 82 | 83 | ## 🚀 Deploy no Vercel 84 | 85 | Esta API está pronta para rodar no Vercel. Após fazer o deploy, configure as variáveis de ambiente no painel do projeto (Settings → Environment Variables): 86 | 87 | * `EMAIL_ADDRESS` 88 | * `EMAIL_PASSWORD` (Use uma Senha de Aplicativo do Google, não a sua senha pessoal) 89 | * `SMTP_SERVER` 90 | * `SMTP_PORT` 91 | 92 | ----- 93 | 94 | ## 🔄 Histórico de Atualizações Recentes 95 | 96 | ### ✅ Anti-AdBlock (Mudança de Rota) 97 | 98 | A rota principal foi alterada de `/track-visit` para `/api/ping`. 99 | 100 | * **Motivo:** Bloqueadores de anúncios (uBlock Origin, AdBlock) e navegadores focados em privacidade bloqueiam automaticamente URLs contendo a palavra "track". 101 | * **Solução:** O uso de um nome neutro (`ping`) garante que a requisição chegue ao servidor e a visita seja contabilizada. 102 | 103 | ### 💾 Persistência de Dados 104 | 105 | Implementado novo módulo `database.py`. 106 | 107 | * **Antes:** O sistema limpava as visitas após enviar o relatório diário. 108 | * **Agora:** O histórico é mantido integralmente. O relatório diário informa quantas visitas ocorreram "Hoje" e qual é o "Total Acumulado" desde o início do projeto. 109 | 110 | ### 🔒 Anti-duplicatas Inteligente 111 | 112 | Sistema que previne spam no seu e-mail. 113 | 114 | * Se o mesmo visitante (ou múltiplos visitantes) acionarem a API várias vezes em menos de 30 segundos, apenas **um** e-mail de notificação imediata será enviado, mas todas as visitas serão contabilizadas no banco de dados. 115 | 116 | 117 | --------------------------------------------------------------------- 118 | 119 | # Visit Notification API 120 | 121 | This project is a simple API developed with Flask (Python) that detects website visits, stores a persistent visit history, and sends email notifications. 122 | 123 | ## What does it do? 124 | 125 | 1. **Immediate Notification**: Sends an instant email for each new visit received on your website. 126 | 127 | 2. **Daily Report**: Sends an automatic summary at 5 PM with: 128 | 129 | * Total visits for the day. 130 | 131 | * **Grand Total Accumulated** (complete history from the beginning). 132 | 133 | * Detailed access times. 134 | 135 | 3. **Data Persistence**: Keeps the visit history saved in a JSON file, ensuring that the count does not reset daily. 136 | 137 | 4. **Anti-AdBlock**: Uses neutral routes (`/api/ping`) to avoid blocking by privacy extensions and browsers like Brave. 138 | 139 | ## 🔗 Endpoints 140 | 141 | * **GET /**: Welcome route. 142 | 143 | * **POST /api/ping**: Route to register a visit (Replaces the old `/track-visit` to avoid AdBlock blocks). 144 | 145 | * **GET /health**: Checks the API status and displays the total number of registered visits. 146 | 147 | * **GET /send-daily-report**: Route triggered by the Cron Job to send the daily summary. 148 | 149 | ## 💻 How to integrate in the Frontend 150 | 151 | To register a visit to your website (React, Next.js, pure HTML, etc.), make a `POST` request to the `/api/ping` route: 152 | 153 | ```javascript 154 | // Integration example 155 | fetch('[https://your-api.vercel.app/api/ping](https://your-api.vercel.app/api/ping)', { 156 | 157 | method: 'POST', 158 | 159 | headers: { 160 | 161 | 'Content-Type': 'application/json' 162 | 163 | }, 164 | 165 | / Optional: send extra data in the body if necessary 166 | 167 | body: JSON.stringify({}) 168 | }) 169 | .then(response => { 170 | 171 | if (response.ok) console.log("Visit registered!"); 172 | 173 | }) 174 | .catch(err => console.error("Error registering visit", err)); 175 | ```` 176 | 177 | ## 🛠 Technologies Used 178 | 179 | * **Python 3 & Flask**: Lightweight serverless backend. 180 | 181 | * **JSON Database**: Data persistence system in a file (`database.py`). 182 | 183 | * **SMTP (Gmail)**: For secure notification delivery. 184 | 185 | * **Vercel Cron**: For automatic scheduling of the daily report. 186 | 187 | * **User-Agents**: Library for detecting and blocking bots. 188 | 189 | ## ⚙️ Local Configuration 190 | 191 | 1. Clone the repository: 192 | 193 | ``bash 194 | git clone [https://github.com/your-username/your-repo.git](https://github.com/your-username/your-repo.git) 195 | 196 | cd your-repo 197 | 198 | `` 199 | 200 | 2. Create a virtual environment and install the dependencies: 201 | 202 | ``bash 203 | python -m venv venv 204 | source venv/bin/activate # On Windows: venv\Scripts\activate 205 | pip install -r requirements.txt 206 | 207 | `` 208 | 209 | 3. Configure the environment variables: 210 | 211 | Create a `.env` file in the root directory (based on `.env.example`) and add: 212 | 213 | ``env 214 | EMAIL_ADDRESS=your_email@gmail.com 215 | EMAIL_PASSWORD=your_application_password 216 | SMTP_SERVER=smtp.gmail.com 217 | SMTP_PORT=587 218 | 219 | 220 | 4. Run the server: 221 | 222 | ``bash 223 | python app.py 224 | 225 | `` 226 | 227 | ## 🚀 Deploy on Vercel 228 | 229 | This API is ready to run on Vercel. After deploying, configure the environment variables in the project panel (Settings → Environment Variables): 230 | 231 | * `EMAIL_ADDRESS` 232 | 233 | * `EMAIL_PASSWORD` (Use a Google App Password, not your personal password) 234 | 235 | * `SMTP_SERVER` 236 | 237 | * `SMTP_PORT` 238 | 239 | ----- 240 | 241 | ## 🔄 Recent Update History 242 | 243 | ### ✅ Anti-AdBlock (Route Change) 244 | 245 | The main route has been changed from `/track-visit` to `/api/ping`. 246 | 247 | * **Reason:** Ad blockers (uBlock Origin, AdBlock) and privacy-focused browsers automatically block URLs containing the word "track". 248 | 249 | * **Solution:** Using a neutral name (`ping`) ensures that the request reaches the server and the visit is counted. 250 | 251 | ### 💾 Data Persistence 252 | 253 | New module `database.py` implemented. 254 | 255 | * **Before:** The system cleared visits after sending the daily report. 256 | 257 | * **Now:** The history is fully maintained. The daily report informs how many visits occurred "Today" and what the "Total Accumulated" is since the beginning of the project. 258 | 259 | ### 🔒 Intelligent Anti-Duplicate 260 | 261 | System that prevents spam in your email. 262 | 263 | * If the same visitor (or multiple visitors) trigger the API several times in less than 30 seconds, only **one** immediate notification email will be sent, but all visits will be counted in the database. 264 | --------------------------------------------------------------------------------