168 lines
9.1 KiB
Python
168 lines
9.1 KiB
Python
# 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}"
|
||
) |