Ekaropolus 6e9881f9d0
All checks were successful
continuous-integration/drone/push Build is passing
refactoring public and private services
2025-07-22 19:26:15 -06:00

207 lines
8.1 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import requests
import logging
from django.core.exceptions import ObjectDoesNotExist
from .models import FacebookPageAssistant
from pxy_openai.assistants import OpenAIAssistant as OpenAIService
from pxy_neo4j.neo4j_connector import Neo4jDatabase
logger = logging.getLogger(__name__)
class FacebookService:
"""
A service to interact with the Facebook Graph API,
generate AI replies, and log interactions.
"""
def __init__(self, user_access_token, facebook_api_version="v22.0"):
self.user_access_token = user_access_token
self.base_url = f"https://graph.facebook.com/{facebook_api_version}"
self.neo4j_db = Neo4jDatabase()
# ─── Workflows / Transactional Methods ────────────────────────────────────
def post_comment_on_share(self, page_id, post_id, message="", sender_id=None):
"""
1. Fetch page token & post details
2. Build AI prompt and get a reply
3. Comment on the shared post + log
4. Comment on the original post (if any) + log
"""
token = self._get_page_token(page_id)
if not token:
return None
details = self._get_post_details(post_id, token)
if not details:
return None
assistant_name = self._get_assistant_name(page_id)
if not assistant_name:
return None
prompt = self._build_share_prompt(message, details["parent_message"])
bot_resp = self._generate_ai_reply(assistant_name, prompt)
# Comment on shared post
mention_target = details.get("parent_from_id") or details.get("from_id")
shared_msg = self._format_comment(bot_resp, mention_target)
shared_resp = self._post_facebook_comment(post_id, shared_msg, token)
if shared_resp:
self._log_interaction(page_id, details["parent_message"] or "Shared post", bot_resp)
# Comment on original post
parent = details.get("parent_id")
if parent and shared_resp:
orig_msg = self._format_comment(bot_resp, mention_target)
orig_resp = self._post_facebook_comment(parent, orig_msg, token)
if orig_resp:
self._log_interaction(page_id, details["parent_message"] or "Original post", bot_resp, suffix=" (Original)")
return shared_resp
def reply_to_comment(self, page_id, comment_id, message):
"""
1. Fetch page token & assistant
2. Generate AI reply
3. Post reply + log
"""
token = self._get_page_token(page_id)
if not token:
return None
assistant_name = self._get_assistant_name(page_id)
if not assistant_name:
return None
incoming = message.strip() or "Thank you for your comment! What do you think about it?"
bot_resp = self._generate_ai_reply(assistant_name, incoming)
resp = self._post_facebook_comment(comment_id, bot_resp, token)
if resp:
self._log_interaction(page_id, incoming, bot_resp, user_prefix="fb_user")
return resp
# ─── Internal Helpers ────────────────────────────────────────────────────
def _get_page_token(self, page_id):
"""Fetch the Pageaccess token via /me/accounts."""
url = f"{self.base_url}/me/accounts?access_token={self.user_access_token}"
try:
res = requests.get(url); res.raise_for_status()
for p in res.json().get("data", []):
if p.get("id") == str(page_id):
return p.get("access_token")
logger.error(f"Page {page_id} not found in accounts")
except Exception as e:
logger.error(f"_get_page_token error: {e}")
return None
def _get_assistant_name(self, page_id):
"""Lookup which OpenAI assistant is bound to this Facebook Page."""
try:
fa = FacebookPageAssistant.objects.get(page_id=page_id)
return fa.assistant.name
except ObjectDoesNotExist:
logger.error(f"No assistant configured for page {page_id}")
return None
def _get_post_details(self, post_id, token):
"""
Returns:
message, description, parent_id, from_id,
parent_from_id, parent_message
"""
try:
# Fetch this post
fields = "message,attachments.limit(1){description},parent_id,from"
url1 = f"{self.base_url}/{post_id}?fields={fields}&access_token={token}"
d1 = requests.get(url1).json()
msg = d1.get("message", "")
desc = d1.get("attachments", {}).get("data",[{}])[0].get("description","")
parent = d1.get("parent_id")
from_id = d1.get("from",{}).get("id")
# If a share, fetch original author & text
p_from = None
p_msg = None
if parent:
url2 = f"{self.base_url}/{parent}?fields=from,message&access_token={token}"
p = requests.get(url2).json()
p_from = p.get("from",{}).get("id")
p_msg = p.get("message","")
return {
"message": msg,
"description": desc,
"parent_id": parent,
"from_id": from_id,
"parent_from_id": p_from,
"parent_message": p_msg,
}
except Exception as e:
logger.error(f"_get_post_details error for {post_id}: {e}")
return {}
def _build_share_prompt(self, incoming, parent_message):
"""Construct the AI prompt for a sharecomment."""
text = parent_message or incoming or ""
if not text:
return "Share an inspiring science fact."
return (
f"You are Polisplexity, an expert citytech consultancy bot. "
f"Read the post: \"{text}\". "
"1) Identify the urban problem. "
"2) Summarize any existing solution or say 'No solution mentioned.' "
"3) Propose a new datadriven solution using digital twins/HPC. "
"4) Ask a question to spark discussion."
)
def _generate_ai_reply(self, assistant_name, prompt):
"""Send the prompt to OpenAI and return the response."""
try:
svc = OpenAIService(name=assistant_name)
return svc.handle_message(prompt)
except Exception as e:
logger.error(f"_generate_ai_reply error: {e}")
return ""
def _format_comment(self, message, sender_id=None):
"""Prepend a link to the sender page + WhatsApp CTA, nicely formatted."""
prefix = f"https://www.facebook.com/{sender_id}" if sender_id else ""
whatsapp = "https://wa.me/447887147696"
parts = [
prefix,
message.strip(),
f"📱 Chat on WhatsApp: {whatsapp}"
]
return "\n\n".join(p for p in parts if p)
def _post_facebook_comment(self, object_id, message, token, sender_id=None):
"""Lowlevel: POST /{object}/comments"""
msg = self._format_comment(message, sender_id)
try:
url = f"{self.base_url}/{object_id}/comments"
resp = requests.post(url, data={"message": msg, "access_token": token})
resp.raise_for_status()
logger.info(f"Posted comment on {object_id}")
return resp.json()
except Exception as e:
logger.error(f"_post_facebook_comment error on {object_id}: {e}")
return None
def _log_interaction(self, page_id, user_msg, bot_msg, user_prefix="fb_bot", suffix=""):
"""Persist the interaction in Neo4j."""
try:
self.neo4j_db.store_interaction(
user_id = f"{user_prefix}_{page_id}",
bot_id = f"fb_bot_{page_id}",
user_message= user_msg,
bot_response= bot_msg,
platform = f"Facebook{suffix}"
)
except Exception as e:
logger.error(f"_log_interaction error: {e}")