webhook 1
from whatsapp.models import ContactAttribute
from chat.models import Chat
from automation.models import Node, FlowExecution, Edge, FlowInteraction
import requests
from contact.models import Contact as C1
from django.utils import timezone
def handle_text_message(message_text, contact, brand, message_id, timestamp):
"""Handle text messages and check for flow triggers"""
# Check for trigger nodes that match the message text
trigger_nodes = Node.objects.filter(
flow__brand=brand,
flow__is_active=True,
node_type='TRIGGER',
trigger_type='KEYWORDS',
)
for node in trigger_nodes:
keywords = node.keywords or []
if any(keyword.lower() in message_text.lower() for keyword in keywords):
# Start flow execution
start_flow_execution(node.flow, contact, node)
# Increment trigger count
node.flow.trigger_count += 1
node.flow.save()
break
def handle_template_button_message(button_text, contact, brand):
"""Handle chatbot triggering when a template button is clicked"""
# Check for the template trigger node
trigger_nodes = Node.objects.filter(
flow__brand=brand,
flow__is_active=True,
node_type='TRIGGER',
trigger_type='TEMPLATE'
)
for node in trigger_nodes:
# Find matching NodeButton where title = clicked button text
matching_button = node.buttons.filter(title=button_text, button_type = "QUICK_REPLY").first()
if matching_button:
# Find the correct edge using button handle_id
edge = Edge.objects.filter(flow=node.flow, source_handle=matching_button.handle_id).first()
if edge:
start_flow_execution_template(node.flow, contact, edge.target_node)
node.flow.trigger_count += 1
node.flow.save()
break
def handle_interactive_message(message, contact, brand, message_id, timestamp):
"""Handle interactive messages (button/list replies)"""
interactive_type = message.get('interactive', {}).get('type')
# Get active flow execution for this contact
flow_execution = FlowExecution.objects.filter(
contact=contact,
status__in=['ACTIVE', 'COMPLETED']).first()
if not flow_execution:
return
if interactive_type == 'button_reply':
button_reply = message['interactive']['button_reply']
handle_button_reply(flow_execution, button_reply, message_id, brand)
elif interactive_type == 'list_reply':
list_reply = message['interactive']['list_reply']
handle_list_reply(flow_execution, list_reply, message_id, brand)
def handle_button_reply(flow_execution, button_reply, message_id, brand):
"""Handle button replies and progress the flow"""
button_id = button_reply.get('id')
button_title = button_reply.get('title')
# Save the incoming button reply
Chat.objects.create(
brand=brand,
contact=flow_execution.contact,
message_id=message_id,
flow=flow_execution.flow,
node=flow_execution.current_node,
is_flow_message=True,
timestamp=timezone.now(),
message_type='interactive',
content=button_title,
button_id=button_id,
button_title=button_title,
is_outgoing=False
)
# Find the next node based on the button pressed
edge = Edge.objects.filter(
flow=flow_execution.flow,
flow__brand=brand,
source_handle=button_id
).first()
if edge:
progress_flow(flow_execution, edge.target_node)
else:
complete_flow(flow_execution)
def handle_list_reply(flow_execution, list_reply, message_id, brand):
"""Handle list replies and progress the flow"""
row_id = list_reply.get('id')
# Save the incoming list reply
Chat.objects.create(
brand=brand,
contact=flow_execution.contact,
message_id=message_id,
flow=flow_execution.flow,
node=flow_execution.current_node,
is_flow_message=True,
timestamp=timezone.now(),
message_type='interactive',
content=list_reply.get('title'),
list_row_id=row_id,
list_row_title=list_reply.get('title'),
list_row_description=list_reply.get('description'),
is_outgoing=False
)
# Find the next node
edge = Edge.objects.filter(
flow=flow_execution.flow,
flow__brand=brand,
source_handle=row_id
).first()
if edge:
progress_flow(flow_execution, edge.target_node)
else:
complete_flow(flow_execution)
def start_flow_execution_template(flow, contact, trigger_node):
"""Start a new flow execution"""
# Cancel any active executions for this contact
FlowExecution.objects.filter(
contact=contact,
status='ACTIVE'
).update(
status='ABANDONED',
completed_at=timezone.now()
)
# Create new flow execution
execution = FlowExecution.objects.create(
flow=flow,
contact=contact,
current_node=trigger_node,
status='ACTIVE'
)
if trigger_node:
progress_flow(execution, trigger_node)
def start_flow_execution(flow, contact, trigger_node):
"""Start a new flow execution"""
# Cancel any active executions for this contact
FlowExecution.objects.filter(
contact=contact,
status='ACTIVE'
).update(
status='ABANDONED',
completed_at=timezone.now()
)
# Create new flow execution
execution = FlowExecution.objects.create(
flow=flow,
contact=contact,
current_node=trigger_node,
status='ACTIVE'
)
# Find first node after trigger
edge = Edge.objects.filter(source_node=trigger_node).first()
if edge:
progress_flow(execution, edge.target_node)
def progress_flow(flow_execution, next_node):
"""Progress the flow to the next node"""
flow_execution.current_node = next_node
flow_execution.save()
if next_node.node_type == 'ATTRIBUTE':
handle_attribute_node(flow_execution)
else:
send_node_message(flow_execution)
def handle_attribute_node(flow_execution):
"""Handle attribute node operations"""
node = flow_execution.current_node
try:
# Set the attribute
ContactAttribute.objects.update_or_create(
contact=flow_execution.contact,
attribute=node.attribute,
defaults={'value': node.attribute_value}
)
# Record the interaction
FlowInteraction.objects.create(
flow_execution=flow_execution,
node=node,
interaction_type='ATTRIBUTE_ACTION',
webhook_message_id='attribute_update'
)
# Find next node
edge = Edge.objects.filter(source_node=node).first()
if edge:
progress_flow(flow_execution, edge.target_node)
else:
# Complete the flow if no next node
complete_flow(flow_execution)
except Exception as e:
print(f"Error in attribute node: {str(e)}")
flow_execution.status = 'ABANDONED'
flow_execution.save()
def handle_ai_node(flow_execution):
"""Handle AI node operations"""
node = flow_execution.current_node
contact = flow_execution.contact
try:
# Check if we've exceeded daily limit
today = timezone.now().date()
daily_interactions = Chat.objects.filter(
contact=contact,
node=node,
is_flow_message=True,
timestamp__date=today,
is_outgoing=False # Only count user messages
).count()
if daily_interactions >= node.daily_limit:
# Find and trigger daily limit fallback
edge = Edge.objects.filter(
source_node=node,
source_handle='limit'
).first()
if edge:
progress_flow(flow_execution, edge.target_node)
return
else:
complete_flow(flow_execution)
return
# Check if activation time has expired
time_diff = timezone.now() - flow_execution.started_at
if time_diff.total_seconds() > (node.activation_time * 60):
# Time exceeded, find and trigger timeout edge
edge = Edge.objects.filter(
source_node=node,
source_handle='timeout'
).first()
if edge:
progress_flow(flow_execution, edge.target_node)
return
else:
complete_flow(flow_execution)
return
# Send AI introduction message
knowledge_base = node.knowledge_base
intro_message = (
f"👋 Hello! I'm your AI assistant powered by {knowledge_base.name}. "
"I'm here to help answer your questions. Feel free to ask anything!"
)
message_data = {
"messaging_product": "whatsapp",
"recipient_type": "individual",
"to": contact.whatsapp_number,
"type": "text",
"text": {"body": intro_message}
}
response = send_whatsapp_message(flow_execution.flow.brand, message_data)
save_outgoing_chat(flow_execution, response, 'text', intro_message)
except Exception as e:
print(f"Error in AI node: {str(e)}")
flow_execution.status = 'ABANDONED'
flow_execution.save()
def handle_data_collection_node(flow_execution):
"""Handle Data Collection node operations"""
node = flow_execution.current_node
contact = flow_execution.contact
try:
# Send the question
message_data = {
"messaging_product": "whatsapp",
"recipient_type": "individual",
"to": contact.whatsapp_number,
"type": "text",
"text": {"body": node.question}
}
response = send_whatsapp_message(flow_execution.flow.brand, message_data)
save_outgoing_chat(flow_execution, response, 'text', node.question)
except Exception as e:
print(f"Error in Data Collection node: {str(e)}")
flow_execution.status = 'ABANDONED'
flow_execution.save()
def complete_flow(flow_execution):
"""Mark a flow execution as completed"""
flow_execution.status = 'COMPLETED'
flow_execution.completed_at = timezone.now()
flow_execution.save()
# Increment completion count
flow = flow_execution.flow
flow.completion_count += 1
flow.save()
def send_node_message(flow_execution):
"""Send appropriate message based on node type"""
node = flow_execution.current_node
if node.node_type == 'TEXT_BUTTON':
send_button_message(flow_execution)
elif node.node_type == 'MEDIA':
send_media_message(flow_execution)
elif node.node_type == 'LIST':
send_list_message(flow_execution)
# Message Sending Functions
def send_button_message(flow_execution):
"""Send button message"""
node = flow_execution.current_node
buttons = node.buttons.all()
body_text = node.body_text.replace("$Name", flow_execution.contact.name) if "$Name" in node.body_text else node.body_text
message_data = {
"messaging_product": "whatsapp",
"recipient_type": "individual",
"to": flow_execution.contact.whatsapp_number,
"type": "interactive",
"interactive": {
"type": "button",
"body": {
"text": body_text
},
"action": {
"buttons": [
{
"type": "reply",
"reply": {
"id": button.handle_id,
"title": button.title
}
} for button in buttons
]
}
}
}
if node.footer_text:
message_data["interactive"]["footer"] = {"text": node.footer_text}
response = send_whatsapp_message(flow_execution.flow.brand, message_data)
save_outgoing_chat(flow_execution, response, 'button', body_text)
def send_media_message(flow_execution):
"""Send media message"""
node = flow_execution.current_node
buttons = node.buttons.all()
body_text = node.body_text.replace("$Name", flow_execution.contact.name) if "$Name" in node.body_text else node.body_text
message_data = {
"messaging_product": "whatsapp",
"recipient_type": "individual",
"to": flow_execution.contact.whatsapp_number,
"type": "interactive",
"interactive": {
"type": "button",
"header": {
"type": node.media_type.lower(),
node.media_type.lower(): {
"link": node.media_url
}
},
"body": {
"text": body_text
},
"action": {
"buttons": [
{
"type": "reply",
"reply": {
"id": button.handle_id,
"title": button.title
}
} for button in buttons
]
}
}
}
if node.footer_text:
message_data["interactive"]["footer"] = {"text": node.footer_text}
response = send_whatsapp_message(flow_execution.flow.brand, message_data)
save_outgoing_chat(flow_execution, response, node.media_type.lower(), body_text)
def send_list_message(flow_execution):
"""Send list message"""
node = flow_execution.current_node
sections = node.sections.all().prefetch_related('rows')
body_text = node.body_text.replace("$Name", flow_execution.contact.name) if "$Name" in node.body_text else node.body_text
message_data = {
"messaging_product": "whatsapp",
"recipient_type": "individual",
"to": flow_execution.contact.whatsapp_number,
"type": "interactive",
"interactive": {
"type": "list",
"body": {
"text": body_text
},
"action": {
"button": node.list_button,
"sections": [
{
"title": section.title,
"rows": [
{
"id": row.handle_id,
"title": row.title,
"description": row.description
} for row in section.rows.all()
]
} for section in sections
]
}
}
}
if node.footer_text:
message_data["interactive"]["footer"] = {"text": node.footer_text}
response = send_whatsapp_message(flow_execution.flow.brand, message_data)
save_outgoing_chat(flow_execution, response, 'button', body_text)
def send_whatsapp_message(brand, message_data):
"""Send message to WhatsApp"""
url = f"https://graph.facebook.com/v22.0/{brand.wb_phone_number_id}/messages"
headers = {
"Authorization": f"Bearer {brand.access_token}",
"Content-Type": "application/json"
}
response = requests.post(url, json=message_data, headers=headers)
return response.json()
def save_outgoing_chat(flow_execution, response, message_type, content):
"""Save outgoing message to Chat"""
message_id = response.get('messages', [{}])[0].get('id')
if message_id:
node = flow_execution.current_node
# Base chat data
chat_data = {
'brand': flow_execution.flow.brand,
'contact': flow_execution.contact,
'message_id': message_id,
'timestamp': timezone.now(),
'message_type': 'interactive', # Using existing interactive type
'content': content,
'is_outgoing': True,
'flow': flow_execution.flow,
'node': node,
'is_flow_message': True,
'footer_text': node.footer_text
}
# Add media-specific data if it's a media message
if message_type.lower() in ['image', 'video', 'document']:
chat_data.update({
'file_id': node.whatsapp_media_id,
'mime_type': node.media_type,
'filename': node.file_name
})
Chat.objects.create(**chat_data)
Comments
Post a Comment