webhook backup
from whatsapp.models import MessageStatus
from django.utils import timezone
from whatsapp.models import Contact, ContactAttribute
from brand.models import Brand
from chat.models import Chat
from automation.models import Node, FlowExecution, Edge, FlowInteraction
import requests
from contact.models import Contact as C1
def handle_messages_event(value):
statuses = value.get('statuses', [])
messages = value.get('messages', [])
# Handling message statuses (sent, delivered, read, failed)
if statuses:
for status in statuses:
message_id = status.get('id')
message_status = status.get('status')
timestamp = int(status.get('timestamp'))
# Convert timestamp to datetime
datetime_value = timezone.datetime.fromtimestamp(timestamp)
# Retrieve and update MessageStatus based on the message ID
try:
message_instance = MessageStatus.objects.get(message_id=message_id)
update_message_status(message_instance, message_status, datetime_value, status)
except MessageStatus.DoesNotExist:
print(f"Message ID {message_id} not found in the database.")
# Handling normal messages (e.g., text, media)
if messages:
for message in messages:
handle_normal_message(message, value)
def update_message_status(message_instance, status, timestamp, failed_status):
"""Update the status of a message based on the webhook data"""
if status == 'sent':
message_instance.sent = True
message_instance.sent_at = timestamp
elif status == 'delivered':
message_instance.delivered = True
message_instance.delivered_at = timestamp
elif status == 'read':
message_instance.read = True
message_instance.read_at = timestamp
elif status == 'failed':
message_instance.failed = True
message_instance.failed_reason = failed_status['errors'][0]['message']
message_instance.failed_at = timestamp
message_instance.save()
def handle_normal_message(message, value):
"""Handle incoming normal messages (text, media)"""
# Define acceptable message types
MESSAGE_TYPES = ['text', 'interactive', 'image', 'video', 'document', 'button']
message_type = message.get('type')
if message_type not in MESSAGE_TYPES:
return
phone_number_id = value['metadata']['phone_number_id']
from_whatsapp_number = message.get('from')
message_id = message.get('id')
timestamp = int(message.get('timestamp'))
datetime_value = timezone.datetime.fromtimestamp(timestamp)
try:
brand = Brand.objects.get(wb_phone_number_id=phone_number_id)
contact, _ = Contact.objects.get_or_create(
brand=brand,
whatsapp_number=from_whatsapp_number,
defaults={'name': value['contacts'][0]['profile']['name'], 'source':"WhatsApp"}
)
except Brand.DoesNotExist:
print(f"Brand with phone_number_id {phone_number_id} not found.")
return
chat_data = {
'brand': brand,
'contact': contact,
'message_id': message_id,
'timestamp': datetime_value,
'message_type': message_type,
}
if message_type == 'text':
message_text = message.get('text', {}).get('body', '')
chat_data['content'] = message_text
handle_text_message(message_text, contact, brand, message_id, datetime_value)
elif message_type == 'button':
button = message.get('button', {})
chat_data['content'] = button.get('text') # Save button text in content
# Check if the button reply matches a template message ID
context = message.get('context', {})
message_id = context.get('id')
try:
message_instance = MessageStatus.objects.get(message_id=message_id)
message_instance.replied = True
message_instance.replied_at = timezone.now()
message_instance.save()
except MessageStatus.DoesNotExist:
print(f"Message ID {message_id} not found in the database.")
elif message_type == 'interactive':
handle_interactive_message(message, contact, brand, message_id, datetime_value)
elif message_type == 'image':
image = message.get('image', {})
chat_data.update({
'file_id': image.get('id'),
'mime_type': image.get('mime_type'),
'sha256': image.get('sha256'),
'content': image.get('caption', '')
})
elif message_type == 'video':
video = message.get('video', {})
chat_data.update({
'file_id': video.get('id'),
'mime_type': video.get('mime_type'),
'sha256': video.get('sha256'),
'content': video.get('caption', '')
})
elif message_type == 'document':
document = message.get('document', {})
chat_data.update({
'file_id': document.get('id'),
'mime_type': document.get('mime_type'),
'filename': document.get('filename'),
'sha256': document.get('sha256'),
'content': document.get('caption', '')
})
else:
return
# Save the Chat instance
Chat.objects.create(**chat_data)
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',
)
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_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(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 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()
message_data = {
"messaging_product": "whatsapp",
"recipient_type": "individual",
"to": flow_execution.contact.whatsapp_number,
"type": "interactive",
"interactive": {
"type": "button",
"body": {
"text": node.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', node.body_text)
def send_media_message(flow_execution):
"""Send media message"""
node = flow_execution.current_node
buttons = node.buttons.all()
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": node.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(), node.body_text)
def send_list_message(flow_execution):
"""Send list message"""
node = flow_execution.current_node
sections = node.sections.all().prefetch_related('rows')
message_data = {
"messaging_product": "whatsapp",
"recipient_type": "individual",
"to": flow_execution.contact.whatsapp_number,
"type": "interactive",
"interactive": {
"type": "list",
"body": {
"text": node.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', node.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