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 Page‑access 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 share‑comment.""" text = parent_message or incoming or "" if not text: return "Share an inspiring science fact." return ( f"You are Polisplexity, an expert city‑tech 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 data‑driven 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): """Low‑level: 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}")