From 84f67bc019cfa1e2ceb81089ccae2dfb4883a7a4 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Sat, 7 Feb 2026 18:16:19 +0000 Subject: [PATCH] v4 --- core/__pycache__/utils.cpython-311.pyc | Bin 0 -> 5055 bytes core/__pycache__/views.cpython-311.pyc | Bin 3851 -> 4344 bytes core/templates/core/dashboard.html | 4 +- core/utils.py | 119 +++++++++++++++++++++++++ core/views.py | 12 ++- requirements.txt | 1 + 6 files changed, 132 insertions(+), 4 deletions(-) create mode 100644 core/__pycache__/utils.cpython-311.pyc create mode 100644 core/utils.py diff --git a/core/__pycache__/utils.cpython-311.pyc b/core/__pycache__/utils.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3e314ba9e59392e3258e7a21fa291a4c17b72794 GIT binary patch literal 5055 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 Mv?g-|glL%SzY)V^761SM delta 1080 zcmZuvO-vI(6yE9nblcr+3x%XmB*g|x142t8Nh>juB5>kD;-MHx+g)gNw@YT1fLMYg zHEQ%A8BfN8UND9W%25(yBpke05>A`oo!c5i(x?Y#76A>uWWT)m_WgWs=G8kMxBFg8 zl9$4^vh<*|!>{^6U~4s&S(l&MKcHm3N@W8ID|@Qc8ctj% z*^Xr!FzY?THeyyd!v+<%ES_PTWOjmzO9u#m8>j>l=q>ogHsR_#dgmGwSnSdG`+gOm zHhu_=(2tl3c*&s^rV|j14uJ|S(=V7M#`HU)9H>w-Q(@L{>N-hx4EJHj7rhtFlBO7D zPMbX-_Yh}LgzaBm3>I{#^vOA+nAdYq?k$>{8s{MivD|w8mQpYci_WPk6fgxv!t*+m z6*!f@W3dH&u53cmE6&13646Q`*lAUtH(;3f?t)%6l?BrvLq&+eAPFS01qUVU84uAH zcMD2#N%V?~L~Rf2=WGhYz0pIF5*we!YT?92Y*$F{3F#V>hRq1@_mS!jjgXFU#*qx$ zNab?`98IoMO@<4ze|lTubgn#`n^C6Jl3~Iq{u4%@-2;FWeV&QnGcM>1{QrU^;-7*Z zGVoEgPW&%|zVZX%X6zLJhGRQ;Mt3d*cm(#vrUHlm+mB1Ep$a?dOIqORYDgZM0f}r!{~HRDOLP|I2S11_%&E#1n86&mSEB*F_uoX_f$kogp+T=r toq(5e1#Fi9F547_Z!|tu+mDD=^eiwGZ2@VU!a#mW)v)b4CLGpj`y1Z6^2z`J diff --git a/core/templates/core/dashboard.html b/core/templates/core/dashboard.html index f1402aa..854fe60 100644 --- a/core/templates/core/dashboard.html +++ b/core/templates/core/dashboard.html @@ -30,7 +30,7 @@
Today's Messages
-
0
+
{{ today_messages_count }}
@@ -107,4 +107,4 @@ -{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/core/utils.py b/core/utils.py new file mode 100644 index 0000000..59f4d34 --- /dev/null +++ b/core/utils.py @@ -0,0 +1,119 @@ +import requests +import logging +from .models import Fanpage, Flow, Node, Edge, ChatSession, MessageLog + +logger = logging.getLogger(__name__) + +def send_fb_message(psid, access_token, message_content): + """ + Sends a message to a Facebook user via the Graph API. + message_content: dict matching Facebook's message object (e.g., {"text": "..."}) + """ + url = f"https://graph.facebook.com/v12.0/me/messages?access_token={access_token}" + payload = { + "recipient": {"id": psid}, + "message": message_content + } + try: + response = requests.post(url, json=payload) + response.raise_for_status() + return response.json() + except Exception as e: + logger.error(f"Error sending message to Facebook: {e}") + return None + +def get_next_node(session, message_text): + """ + Determines the next node in the flow based on user input. + """ + fanpage = session.fanpage + + if not session.current_node: + # Start of conversation: find the default flow and its start node + flow = Flow.objects.filter(fanpage=fanpage, is_default=True).first() + if not flow: + flow = Flow.objects.filter(fanpage=fanpage).first() + + if flow: + return Node.objects.filter(flow=flow, is_start_node=True).first() + return None + + # We are in an active session, look for a matching edge + edges = Edge.objects.filter(source_node=session.current_node) + message_text_clean = message_text.strip().lower() + + for edge in edges: + if edge.condition.lower() == message_text_clean: + return edge.target_node + + # If no matching edge, we might want to stay at the current node or find a global start + # For now, let's just return the current node (re-prompting) if it was a text node, + # or None if we don't know what to do. + return session.current_node + +def handle_webhook_event(data): + """ + Main entry point for processing Facebook webhook POST data. + """ + if data.get('object') != 'page': + return + + for entry in data.get('entry', []): + for messaging_event in entry.get('messaging', []): + sender_id = messaging_event.get('sender', {}).get('id') + recipient_id = messaging_event.get('recipient', {}).get('id') + + if not sender_id or not recipient_id: + continue + + # 1. Identify Fanpage + try: + fanpage = Fanpage.objects.get(page_id=recipient_id, is_active=True) + except Fanpage.DoesNotExist: + logger.warning(f"Received event for unknown or inactive Page ID: {recipient_id}") + continue + + # 2. Extract Message + message_text = "" + if 'message' in messaging_event: + message_text = messaging_event['message'].get('text', "") + elif 'postback' in messaging_event: + # Handle button clicks + message_text = messaging_event['postback'].get('payload', "") + + if not message_text: + continue + + # 3. Get or Create Session + session, created = ChatSession.objects.get_or_create( + psid=sender_id, + fanpage=fanpage + ) + + # 4. Log User Message + MessageLog.objects.create( + session=session, + sender_type='user', + message_text=message_text + ) + + # 5. Determine Next Node + next_node = get_next_node(session, message_text) + + if next_node: + # 6. Send Reply + # next_node.content is a JSONField, expected to be {"text": "..."} or similar + result = send_fb_message(sender_id, fanpage.access_token, next_node.content) + + if result: + # 7. Update Session + session.current_node = next_node + session.save() + + # 8. Log Bot Response + bot_text = next_node.content.get('text', '[Non-text message]') + MessageLog.objects.create( + session=session, + sender_type='bot', + message_text=bot_text + ) diff --git a/core/views.py b/core/views.py index 206641c..4bf003d 100644 --- a/core/views.py +++ b/core/views.py @@ -2,8 +2,10 @@ from django.shortcuts import render, redirect, get_object_or_404 from django.contrib.auth.decorators import login_required from django.views.decorators.csrf import csrf_exempt from django.http import HttpResponse +from django.utils import timezone import json from .models import Fanpage, Flow, MessageLog, ChatSession +from .utils import handle_webhook_event def home(request): if request.user.is_authenticated: @@ -16,11 +18,16 @@ def dashboard(request): flows = Flow.objects.all() recent_logs = MessageLog.objects.order_by('-timestamp')[:10] + # Count messages from today + today = timezone.now().date() + today_messages_count = MessageLog.objects.filter(timestamp__date=today).count() + context = { 'fanpage_count': fanpages.count(), 'flow_count': flows.count(), 'fanpages': fanpages, 'recent_logs': recent_logs, + 'today_messages_count': today_messages_count, } return render(request, 'core/dashboard.html', context) @@ -54,10 +61,11 @@ def webhook(request): # Handle incoming messages try: data = json.loads(request.body.decode('utf-8')) - # Process the webhook payload here in the future - # For now, just return 200 OK + # Process the webhook payload + handle_webhook_event(data) return HttpResponse('EVENT_RECEIVED') except Exception as e: + # Log the error if necessary return HttpResponse('Error processing request', status=400) return HttpResponse('Method not allowed', status=405) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index e22994c..d9ed9a4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ Django==5.2.7 mysqlclient==2.2.7 python-dotenv==1.1.1 +requests==2.31.0 \ No newline at end of file