Consultme/app/api/webhook.py
2025-06-21 16:19:21 +00:00

168 lines
9.1 KiB
Python
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# app/api/webhook.py
from fastapi import APIRouter, Request, HTTPException, status
from fastapi.responses import PlainTextResponse, JSONResponse, Response
from pydantic import BaseModel
import json
from config import VERIFY_TOKEN
from models.webhook_model import WebhookEvent
from services.webhook_service import (
handle_message_type, mark_message_as_read,
encrypt_flow_response_data, decrypt_flow_request_data, send_whatsapp_flow # <--- ADICIONAR decrypt_flow_request_data
)
router = APIRouter()
class SendFlowRequest(BaseModel):
to_number: str # O número para onde enviar o Flow (ex: '5582912345678')
# ... (Endpoint GET /webhook) ...
# --- Endpoint para Receber Requisições POST (Incluindo TODOS os Payloads Criptografados e Webhook Padrão) ---
@router.post("/webhook")
async def process_whatsapp_webhook(request: Request):
try:
body = await request.json()
except json.JSONDecodeError as e:
print(f"⚠️ Requisição POST recebida com corpo não-JSON ou JSON inválido: {e}")
return JSONResponse(status_code=status.HTTP_200_OK, content={"status": "corpo inválido, ignorado"})
except Exception as e:
print(f"⚠️ Erro inesperado ao ler o corpo da requisição POST: {e}")
return JSONResponse(status_code=status.HTTP_200_OK, content={"status": "erro de leitura, ignorado"})
# --- Lógica para Requisições de Dados de Flow Criptografadas (incluindo Health Check criptografado) ---
if isinstance(body, dict) and "encrypted_flow_data" in body and "encrypted_aes_key" in body and "initial_vector" in body:
print("🔒 Recebido payload de Flow criptografado.")
try:
# Descriptografa a requisição para obter os dados, a chave AES e o IV originais
decrypted_result = decrypt_flow_request_data(
body["encrypted_flow_data"],
body["encrypted_aes_key"],
body["initial_vector"]
)
decrypted_data = decrypted_result["decrypted_payload"]
aes_key_from_request = decrypted_result["aes_key"]
iv_from_request = decrypted_result["initial_vector"]
print(f"🔓 Dados do Flow descriptografados: {json.dumps(decrypted_data, indent=2)}")
flow_response_data = {
"data": {
"status": "active"
}
}
# Processa o tipo de requisição descriptografada
if decrypted_data.get("action") == "ping":
print("💚 Descriptografado: Ping de dados do Flow. Preparando resposta 'active'.")
else:
print(f"❓ Descriptografado: Outros dados de Flow. Conteúdo: {decrypted_data}")
flow_response_data = {"status": "success", "message": "Dados de Flow processados."}
# Criptografa a resposta usando a chave AES e o IV DA REQUISIÇÃO ORIGINAL
# A função encrypt_flow_response_data AGORA RETORNA A STRING BASE64 DIRETA
encrypted_final_response_string = encrypt_flow_response_data(
flow_response_data,
aes_key_from_request,
iv_from_request
)
print(f"DEBUG: Enviando resposta final criptografada (Base64): {encrypted_final_response_string[:50]}...")
return Response(content=encrypted_final_response_string, media_type="text/plain")
except ValueError as ve:
print(f"❌ ERRO de chave ou dados para descriptografia/criptografia de Flow: {ve}")
return JSONResponse(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, content={"status": f"Erro de chave ou criptografia: {ve}"})
except Exception as e:
print(f"❌ ERRO ao processar dados de Flow criptografados: {e}")
return JSONResponse(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, content={"status": f"Erro interno ao processar Flow: {e}"})
# --- Lógica para Eventos de Webhook Padrão do WhatsApp (mensagens, status) ---
# Este bloco só será acionado se não for um payload de Flow criptografado
# --- Bloco para Eventos Padrão do WhatsApp (mensagens, status, etc.) ---
# Este é o bloco que interessa para o teste de sessão
elif isinstance(body, dict) and "object" in body and "entry" in body:
try:
event = WebhookEvent(**body)
if event.object != 'whatsapp_business_account':
print("❌ Evento recebido, mas não é do tipo 'whatsapp_business_account'. Ignorando.")
return JSONResponse(status_code=status.HTTP_404_NOT_FOUND, content={"status": "ignorado"})
for entry in event.entry:
for change in entry.changes:
if change.field == 'messages':
if change.value and change.value.messages:
for message in change.value.messages:
print(f"\n💬 Mensagem recebida de {message.from_} (Tipo: {message.type}, ID: {message.id})")
await mark_message_as_read(message.id)
# CHAME handle_message_type PASSANDO O OBJETO 'message' COMPLETO
await handle_message_type(message)
else:
print("⚠️ Evento de 'messages' recebido, mas sem mensagens válidas no payload.")
elif change.field == 'statuses':
if change.value and change.value.statuses:
for status_event in change.value.statuses:
print(f" Status da mensagem ID {status_event.id}: {status_event.status} (para {status_event.recipient_id})")
# Futuramente: handle_status_event(status_event)
else:
print("⚠️ Evento de 'statuses' recebido, mas sem status válidos no payload.")
# REMOVA OU COMENTE TEMPORARIAMENTE o bloco 'elif change.field == 'flows':' se estiver lá
# para focar apenas em mensagens/sessão.
# elif change.field == 'flows':
# ...
else:
print(f"⚠️ Evento de campo desconhecido recebido: {change.field}. Payload completo: {change.model_dump_json(indent=2) if hasattr(change, 'model_dump_json') else json.dumps(change.dict(), indent=2)}")
return JSONResponse(status_code=status.HTTP_200_OK, content={"status": "evento processado com sucesso"})
except Exception as e:
print(f"⚠️ Payload recebido não corresponde ao modelo WebhookEvent ou erro interno: {type(e).__name__}: {e}. Payload: {json.dumps(body)}")
return JSONResponse(status_code=status.HTTP_200_OK, content={"status": "payload desconhecido, ignorado"})
# --- Fallback para payloads POST não reconhecidos ---
else:
print(f"❓ Payload POST recebido que não é Health Check, Flow criptografado, nem WebhookEvent padrão. Ignorando. Payload: {json.dumps(body)}")
return JSONResponse(status_code=status.HTTP_200_OK, content={"status": "payload não reconhecido, ignorado"})
@router.post("/send_cadastro_flow")
async def trigger_cadastro_flow(request_data: SendFlowRequest):
"""
Dispara o Flow de cadastro do WhatsApp para um número específico.
"""
target_number = request_data.to_number # <-- ESTA LINHA DEVE ESTAR AQUI E ACESSÍVEL
# --- MUITO IMPORTANTE: Substitua pelo FLOW_ID REAL que você publicou ---
FLOW_ID_DO_SEU_CADASTRO_PUBLICADO = 1094799999286205
if FLOW_ID_DO_SEU_CADASTRO_PUBLICADO == "COLOQUE_AQUI_O_FLOW_ID_DO_SEU_CADASTRO_PUBLICADO":
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Por favor, substitua 'COLOQUE_AQUI_O_FLOW_ID_DO_SEU_CADASTRO_PUBLICADO' pelo Flow ID real no código."
)
print(f"DEBUG: Disparando Flow de Cadastro (ID: {FLOW_ID_DO_SEU_CADASTRO_PUBLICADO}) para o número: {target_number}")
try:
response = await send_whatsapp_flow(target_number, FLOW_ID_DO_SEU_CADASTRO_PUBLICADO, "Abrir Formulário de Cadastro")
if response and response.get("status") == "success":
return JSONResponse(
status_code=status.HTTP_200_OK,
content={"message": f"Flow de cadastro enviado com sucesso para {target_number}", "whatsapp_api_response": response["data"]}
)
else:
print(f"DEBUG: send_whatsapp_flow retornou erro: {response.get('message', 'Erro desconhecido')}")
raise HTTPException(
status_code=response.get("code", status.HTTP_500_INTERNAL_SERVER_ERROR),
detail=f"Falha ao enviar Flow: {response.get('message', 'Erro desconhecido')}"
)
except HTTPException:
raise
except Exception as e:
print(f"❌ ERRO INESPERADO na trigger_cadastro_flow: {type(e).__name__}: {e}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Erro interno ao disparar Flow: {e}"
)