490 lines
18 KiB
Python
490 lines
18 KiB
Python
import json
|
|
import logging
|
|
import string
|
|
import threading
|
|
from typing import Iterable
|
|
|
|
from bs4 import BeautifulSoup
|
|
from django.contrib import messages
|
|
from django.db.models.signals import post_save
|
|
from django.dispatch import receiver
|
|
from django.http.response import HttpResponse
|
|
from django.shortcuts import render
|
|
from django.views.decorators.csrf import csrf_exempt
|
|
|
|
from base.models import Announcement
|
|
from employee.models import Employee
|
|
from horilla.horilla_middlewares import _thread_locals
|
|
from notifications.signals import notify
|
|
from whatsapp.flows import (
|
|
get_asset_category_flow_json,
|
|
get_attendance_request_json,
|
|
get_bonus_point_json,
|
|
get_leave_request_json,
|
|
get_reimbursement_request_json,
|
|
get_shift_request_json,
|
|
get_work_type_request_json,
|
|
)
|
|
from whatsapp.models import WhatsappCredientials
|
|
from whatsapp.utils import (
|
|
asset_request_create,
|
|
attendance_request_create,
|
|
bonus_point_create,
|
|
create_flow,
|
|
create_help_message,
|
|
create_template_buttons,
|
|
create_welcome_message,
|
|
leave_request_create,
|
|
publish_flow,
|
|
reimbursement_create,
|
|
send_document_message,
|
|
send_flow_message,
|
|
send_image_message,
|
|
send_template_message,
|
|
send_text_message,
|
|
shift_create,
|
|
update_flow,
|
|
work_type_create,
|
|
)
|
|
|
|
DETAILED_FLOW = [
|
|
{
|
|
"template_name": "leave",
|
|
"flow_name": "leave_request_flow",
|
|
"flow_json": get_leave_request_json(),
|
|
},
|
|
{
|
|
"template_name": "shift",
|
|
"flow_name": "shift_request",
|
|
"flow_json": get_shift_request_json(),
|
|
},
|
|
{
|
|
"template_name": "bonus_point",
|
|
"flow_name": "bonus_point_flow",
|
|
"flow_json": get_bonus_point_json(),
|
|
},
|
|
{
|
|
"template_name": "reimbursement",
|
|
"flow_name": "reimbursement_flow",
|
|
"flow_json": get_reimbursement_request_json(),
|
|
},
|
|
{
|
|
"template_name": "work_type",
|
|
"flow_name": "work_type_flow",
|
|
"flow_json": get_work_type_request_json(),
|
|
},
|
|
{
|
|
"template_name": "attendance",
|
|
"flow_name": "attendance_flow",
|
|
"flow_json": get_attendance_request_json(),
|
|
},
|
|
{
|
|
"template_name": "asset",
|
|
"flow_name": "asset_flow",
|
|
"flow_json": get_asset_category_flow_json(),
|
|
},
|
|
]
|
|
|
|
processed_messages = set()
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def clean_string(s):
|
|
"""
|
|
Cleans a given string by removing punctuation and converting it to lowercase.
|
|
|
|
Args:
|
|
s (str): The string to clean.
|
|
|
|
Returns:
|
|
str: The cleaned string, or the original string if an error occurs.
|
|
"""
|
|
|
|
try:
|
|
translator = str.maketrans("", "", string.punctuation + " _")
|
|
cleaned_string = s.translate(translator).lower()
|
|
return cleaned_string
|
|
except:
|
|
return s
|
|
|
|
|
|
@csrf_exempt
|
|
def whatsapp(request):
|
|
"""
|
|
Handles incoming WhatsApp webhook requests.
|
|
|
|
Args:
|
|
request (HttpRequest): The incoming HTTP request.
|
|
|
|
Returns:
|
|
HttpResponse: A response indicating the status of the operation.
|
|
"""
|
|
|
|
if request.method == "GET":
|
|
credentials = WhatsappCredientials.objects.first()
|
|
|
|
token = request.GET.get("hub.verify_token")
|
|
challenge = request.GET.get("hub.challenge")
|
|
if token == credentials.meta_webhook_token:
|
|
return HttpResponse(challenge, status=200)
|
|
|
|
if request.method == "POST":
|
|
data = json.loads(request.body)
|
|
if "object" in data and "entry" in data:
|
|
if data["object"] == "whatsapp_business_account":
|
|
for entry in data["entry"]:
|
|
changes = entry.get("changes", [])[0]
|
|
value = changes.get("value", {})
|
|
|
|
if "messages" in value:
|
|
try:
|
|
metadata = value.get("metadata", {})
|
|
contacts = value.get("contacts", [])[0]
|
|
messages = value.get("messages", [])[0]
|
|
|
|
message_id = messages.get("id")
|
|
if message_id in processed_messages:
|
|
continue
|
|
|
|
processed_messages.add(message_id)
|
|
|
|
profile_name = contacts.get("profile", {}).get("name")
|
|
from_number = messages.get("from")
|
|
text = messages.get("text", {}).get("body")
|
|
type = messages.get("type", {})
|
|
flow_response = (
|
|
messages.get("interactive", {})
|
|
.get("nfm_reply", {})
|
|
.get("response_json", {})
|
|
)
|
|
|
|
if type == "interactive":
|
|
flow_conversion(from_number, flow_response)
|
|
if type == "button":
|
|
text = messages.get("button", {}).get("text", {})
|
|
|
|
text = clean_string(text)
|
|
|
|
# Handle different messages based on cleaned text
|
|
if text == "helloworld":
|
|
send_template_message(from_number, "hello_world")
|
|
elif text == "help":
|
|
send_template_message(from_number, "help_50")
|
|
elif text in ["asset", "assetrequest"]:
|
|
send_flow_message(from_number, "asset")
|
|
elif text in ["shift", "shiftrequest"]:
|
|
send_flow_message(from_number, "shift")
|
|
elif text in ["worktype", "worktyperequest"]:
|
|
send_flow_message(from_number, "work_type")
|
|
elif text in ["attendance", "attendancerequest"]:
|
|
send_flow_message(from_number, "attendance")
|
|
elif text in ["leave", "leaverequest"]:
|
|
send_flow_message(from_number, "leave")
|
|
elif text in ["reimbursement", "reimbursementrequest"]:
|
|
send_flow_message(from_number, "reimbursement")
|
|
elif text in ["bonus", "bonuspoint", "bonuspointredeem"]:
|
|
send_flow_message(from_number, "bonus_point")
|
|
elif text in [
|
|
"hi",
|
|
"hello",
|
|
"goodmorning",
|
|
"goodafternoon",
|
|
"goodevening",
|
|
"goodnight",
|
|
"hlo",
|
|
]:
|
|
send_template_message(from_number, "welcome_message_50")
|
|
elif text == "image":
|
|
try:
|
|
image_relative_url = (
|
|
Employee.objects.filter(phone=from_number)
|
|
.first()
|
|
.employee_profile.url
|
|
)
|
|
image_link = request.build_absolute_uri(
|
|
image_relative_url
|
|
)
|
|
except Exception as e:
|
|
print(e)
|
|
|
|
send_image_message(from_number, image_link)
|
|
elif text == "document":
|
|
try:
|
|
document_relative_url = (
|
|
Employee.objects.filter(phone=from_number)
|
|
.first()
|
|
.employee_profile.url
|
|
)
|
|
document_link = request.build_absolute_uri(
|
|
document_relative_url
|
|
)
|
|
except Exception as e:
|
|
print(e)
|
|
|
|
send_document_message(from_number, document_link)
|
|
elif text == "string":
|
|
send_text_message(
|
|
from_number, "test message", "test heading"
|
|
)
|
|
else:
|
|
if text:
|
|
send_template_message(
|
|
from_number, "button_template_50"
|
|
)
|
|
|
|
except KeyError as e:
|
|
print(f"KeyError: {e}")
|
|
return HttpResponse("Bad Request", status=400)
|
|
except Exception as e:
|
|
print(f"Unexpected error: {e}")
|
|
return HttpResponse("Internal Server Error", status=500)
|
|
|
|
return HttpResponse("Message processed", status=403)
|
|
|
|
return HttpResponse("error", status=200)
|
|
|
|
|
|
def create_generic_templates(request, id):
|
|
"""
|
|
Creates generic message templates for WhatsApp.
|
|
|
|
Args:
|
|
request (HttpRequest): The incoming HTTP request.
|
|
|
|
Returns:
|
|
HttpResponse: A response indicating the success or failure of template creation.
|
|
"""
|
|
|
|
try:
|
|
create_template_buttons(id)
|
|
create_welcome_message(id)
|
|
create_help_message(id)
|
|
create_flows(id)
|
|
|
|
credential = WhatsappCredientials.objects.get(id=id)
|
|
credential.created_templates = True
|
|
credential.save()
|
|
|
|
messages.success(request, "Message templates and flows created successfully.")
|
|
except:
|
|
messages.error(request, "Message templates and flows creation failed.")
|
|
return HttpResponse("<script>window.location.reload();</script>")
|
|
|
|
|
|
@csrf_exempt
|
|
def create_flows(cred_id):
|
|
"""
|
|
Creates and publishes flows based on predefined details.
|
|
|
|
Args:
|
|
request (HttpRequest): The incoming HTTP request.
|
|
|
|
Returns:
|
|
HttpResponse: A response indicating the success or failure of flow creation.
|
|
"""
|
|
|
|
try:
|
|
for flow in DETAILED_FLOW:
|
|
template_name = flow["template_name"]
|
|
flow_name = flow["flow_name"]
|
|
flow_json = flow["flow_json"]
|
|
credential = WhatsappCredientials.objects.get(id=cred_id)
|
|
|
|
# Create flow
|
|
create_response = create_flow(flow_name, template_name, cred_id)
|
|
create_response_data = create_response.json()
|
|
|
|
flow_id = create_response_data.get("id")
|
|
if not flow_id:
|
|
return HttpResponse(
|
|
json.dumps(create_response_data),
|
|
status=create_response.status_code,
|
|
content_type="application/json",
|
|
)
|
|
|
|
# Update flow
|
|
update_response = update_flow(flow_id, flow_json, credential.meta_token)
|
|
update_response_data = update_response.json()
|
|
if update_response_data.get("validation_error", {}):
|
|
return HttpResponse(
|
|
json.dumps(update_response_data),
|
|
status=update_response.status_code,
|
|
content_type="application/json",
|
|
)
|
|
|
|
# Publish flow
|
|
publish_response = publish_flow(flow_id, credential.meta_token)
|
|
publish_response_data = publish_response.json()
|
|
if publish_response_data.get("error", {}):
|
|
return HttpResponse(
|
|
json.dumps(publish_response_data),
|
|
status=publish_response.status_code,
|
|
content_type="application/json",
|
|
)
|
|
|
|
return HttpResponse(
|
|
json.dumps({"message": "Flow created successfully"}),
|
|
status=200,
|
|
content_type="application/json",
|
|
)
|
|
|
|
except Exception as e:
|
|
print(f"Unexpected error: {e}")
|
|
return HttpResponse(
|
|
json.dumps({"error": str(e)}), status=500, content_type="application/json"
|
|
)
|
|
|
|
|
|
def send_notification_task(recipient, verb, redirect, icon):
|
|
"""
|
|
Background task to send a notification message via WhatsApp.
|
|
"""
|
|
try:
|
|
request = getattr(_thread_locals, "request", None)
|
|
link = request.build_absolute_uri(redirect) if redirect else None
|
|
message = f"{verb}\nFor more details, \n{link}." if link else verb
|
|
|
|
recipients = (
|
|
recipient
|
|
if isinstance(recipient, Iterable) and not isinstance(recipient, str)
|
|
else [recipient]
|
|
)
|
|
|
|
for user in recipients:
|
|
phone_number = user.employee_get.phone
|
|
if phone_number:
|
|
send_text_message(phone_number, message)
|
|
else:
|
|
print(f"No phone number available for recipient {user}")
|
|
|
|
except Exception as e:
|
|
print(f"Error in notification task: {e}")
|
|
|
|
|
|
# @receiver(notify)
|
|
def send_notification_on_whatsapp(sender, recipient, verb, redirect, icon, **kwargs):
|
|
|
|
thread = threading.Thread(
|
|
target=send_notification_task, args=(recipient, verb, redirect, icon)
|
|
)
|
|
thread.start()
|
|
|
|
|
|
def send_announcement_task(instance, request):
|
|
"""
|
|
Background task to send an announcement message via WhatsApp.
|
|
"""
|
|
employees = instance.employees.all()
|
|
header = instance.title
|
|
body = instance.description
|
|
|
|
soup = BeautifulSoup(body, "html.parser")
|
|
paragraphs = []
|
|
for element in soup.find_all(["p", "ul", "ol", "h1", "h2", "h3", "h4", "h5", "h6"]):
|
|
if element.name in ["h1", "h2", "h3", "h4", "h5", "h6"]:
|
|
heading_text = element.get_text(strip=True).capitalize()
|
|
paragraphs.append(f"*{heading_text}*")
|
|
|
|
elif element.name in ["ul", "ol"]:
|
|
list_items = []
|
|
for li in element.find_all("li"):
|
|
item_text = []
|
|
for child in li.children:
|
|
if child.name == "code":
|
|
item_text.append(f"`{child.get_text()}`")
|
|
elif child.name in ["strong", "b"]:
|
|
item_text.append(f"*{child.get_text()}*")
|
|
elif child.name == "span":
|
|
item_text.append(child.get_text())
|
|
elif child.name == "a":
|
|
item_text.append(child.get_text())
|
|
elif isinstance(child, str):
|
|
item_text.append(child.strip())
|
|
list_items.append("• " + " ".join(item_text).strip())
|
|
paragraphs.append("\n".join(list_items))
|
|
|
|
elif element.name == "p":
|
|
para_text = []
|
|
for child in element.children:
|
|
if child.name == "code":
|
|
para_text.append(f"`{child.get_text()}`")
|
|
elif child.name in ["strong", "b"]:
|
|
para_text.append(f"*{child.get_text()}*")
|
|
elif child.name == "span":
|
|
para_text.append(child.get_text())
|
|
elif child.name == "a":
|
|
para_text.append(child.get_text())
|
|
elif isinstance(child, str):
|
|
para_text.append(child.strip())
|
|
paragraphs.append(" ".join(para_text).strip())
|
|
final_text = "\n\n".join(paragraphs)
|
|
|
|
for employee in employees:
|
|
number = employee.phone
|
|
send_text_message(number, final_text, header)
|
|
for attachment in instance.attachments.all():
|
|
link = attachment.file.url
|
|
document_link = request.build_absolute_uri(link)
|
|
send_document_message(number, document_link)
|
|
|
|
|
|
# @receiver(post_save, sender=Announcement)
|
|
def send_announcement_on_whatsapp(sender, instance, created, **kwargs):
|
|
if not created:
|
|
request = getattr(_thread_locals, "request", None)
|
|
|
|
thread = threading.Thread(
|
|
target=send_announcement_task, args=(instance, request)
|
|
)
|
|
thread.start()
|
|
|
|
|
|
def flow_conversion(number, flow_response_json):
|
|
"""
|
|
Processes a flow response based on the type of request.
|
|
|
|
Args:
|
|
number (str): The phone number of the employee.
|
|
flow_response_json (str): The JSON response from the flow.
|
|
|
|
Returns:
|
|
Response: The response from sending a message.
|
|
"""
|
|
|
|
employee = Employee.objects.filter(phone=number).first()
|
|
flow_response = json.loads(flow_response_json)
|
|
message = "Something went wrong ......"
|
|
type = flow_response["type"]
|
|
|
|
if type == "shift_request":
|
|
message = shift_create(employee, flow_response)
|
|
elif type == "leave_request":
|
|
message = leave_request_create(employee, flow_response)
|
|
elif type == "work_type":
|
|
message = work_type_create(employee, flow_response)
|
|
elif type == "asset_request":
|
|
message = asset_request_create(employee, flow_response)
|
|
elif type == "attendance_request":
|
|
message = attendance_request_create(employee, flow_response)
|
|
elif type == "bonus_point":
|
|
message = bonus_point_create(employee, flow_response)
|
|
elif type == "reimbursement":
|
|
message = reimbursement_create(employee, flow_response)
|
|
|
|
response = send_text_message(number, message)
|
|
return response
|
|
|
|
|
|
def whatsapp_credential_view(request):
|
|
"""
|
|
Renders the WhatsApp credentials view.
|
|
|
|
Args:
|
|
request (HttpRequest): The incoming HTTP request.
|
|
|
|
Returns:
|
|
HttpResponse: The rendered credentials view.
|
|
"""
|
|
|
|
return render(request, "whatsapp/credentials_view.html", {})
|