From 7290e575f62033c81fb5e68cebe43d991951a8db Mon Sep 17 00:00:00 2001 From: Ekaropolus Date: Tue, 22 Jul 2025 13:38:08 -0600 Subject: [PATCH] Adding post id to debbug --- pxy_meta_pages/services.py | 371 ++++++++++++++++++++++--------------- 1 file changed, 217 insertions(+), 154 deletions(-) diff --git a/pxy_meta_pages/services.py b/pxy_meta_pages/services.py index ecb7e1a..7522763 100644 --- a/pxy_meta_pages/services.py +++ b/pxy_meta_pages/services.py @@ -1,9 +1,8 @@ import requests +from pxy_openai.assistants import OpenAIAssistant as OpenAIService # Import the assistant service import logging -from django.core.exceptions import ObjectDoesNotExist - from .models import FacebookPageAssistant -from pxy_openai.assistants import OpenAIAssistant as OpenAIService +from django.core.exceptions import ObjectDoesNotExist from pxy_neo4j.neo4j_connector import Neo4jDatabase logger = logging.getLogger(__name__) @@ -11,186 +10,250 @@ logger = logging.getLogger(__name__) class FacebookService: """ - A service to interact with the Facebook Graph API, - generate AI responses, and log interactions. + A service to interact with the Facebook Graph API. """ 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() + self.facebook_api_version = facebook_api_version + self.base_url = f"https://graph.facebook.com/{self.facebook_api_version}" + self.neo4j_db = Neo4jDatabase() # Initialize Neo4j connection - # ─── Public API ───────────────────────────────────────────────────────────── - - def post_comment_on_share(self, page_id, post_id, incoming_msg="", sender_id=None): + def get_system_user_id(self): """ - Orchestrates commenting on a shared post, then on the original. + Retrieves the system user ID using the user access token. """ - token = self._get_page_token(page_id) - if not token: - return None - - details = self._fetch_post_details(post_id, token) - if not details: - return None - - assistant_name = self._get_assistant_name(page_id) - if not assistant_name: - return None - - # build and send comment on the shared post - prompt = self._build_share_prompt(incoming_msg, details["description"]) - bot_resp = self._generate_ai_reply(assistant_name, prompt) - - # mention the original page if provided - mention_target = details.get("parent_from_id") or details.get("from_id") - shared_msg = self._prepend_mention(bot_resp, mention_target) - - shared_resp = self._post_comment(post_id, shared_msg, token) - if shared_resp: - self._log_interaction(page_id, details["description"] or "Shared post", bot_resp) - - # now comment on the original post - parent = details.get("parent_id") - if parent and shared_resp: - original_msg = self._prepend_mention(bot_resp, mention_target) - orig_resp = self._post_comment(parent, original_msg, token) - if orig_resp: - self._log_interaction(page_id, details["description"] or "Original post", bot_resp, suffix=" (Original)") - - return shared_resp - - def reply_to_comment(self, page_id, comment_id, incoming_msg=""): - """ - Replies to a specific comment. - """ - 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 - - msg = incoming_msg.strip() or "Thank you for your comment! What do you think about it?" - bot_resp = self._generate_ai_reply(assistant_name, msg) - - resp = self._post_comment(comment_id, bot_resp, token) - if resp: - self._log_interaction(page_id, msg, bot_resp, user_prefix="fb_user") - return resp - - # ─── Helpers: token & assistant ───────────────────────────────────────────── - - def _get_page_token(self, page_id): - url = f"{self.base_url}/me/accounts?access_token={self.user_access_token}" try: - resp = requests.get(url) - resp.raise_for_status() - for p in resp.json().get("data", []): - if p.get("id") == str(page_id): - return p.get("access_token") - logger.error(f"Page {page_id} not found in your accounts") - except Exception as e: - logger.error(f"Error fetching Page token: {e}") + return '122106889202727657' + except requests.exceptions.RequestException as e: + logger.error(f"Error fetching system user ID: {e}") return None - def _get_assistant_name(self, page_id): + def get_page_access_token(self, page_id): + """ + Retrieves the Page Access Token for a specific Page ID. + """ + url = f"{self.base_url}/122106889202727657/accounts?access_token={self.user_access_token}" try: - return FacebookPageAssistant.objects.get(page_id=page_id).assistant.name - except ObjectDoesNotExist: - logger.error(f"No OpenAI assistant configured for page {page_id}") + response = requests.get(url) + response.raise_for_status() + data = response.json() + + if "data" in data: + for page in data["data"]: + if page.get("id") == str(page_id): + page_name = page.get("name", "Unknown") + access_token = page.get("access_token", "No Token") + logger.info(f"Retrieved access token for page {page_id}: {page_name}") + return access_token + logger.error(f"Error: Page ID {page_id} not found.") + else: + logger.error("Error: Unexpected response format from Facebook API.") + except requests.exceptions.RequestException as e: + logger.error(f"Error fetching Page Access Token: {e}") + return None + + def post_comment_on_share(self, page_id, post_id, message, sender_id=None): + """ + Posts a comment on a shared post using the Facebook API. + Fetches post details (description, parent_id) to improve the comment. + If parent_id exists, posts the same comment on the original post. + """ + # Retrieve the Page Access Token dynamically + page_access_token = self.get_page_access_token(page_id) + if not page_access_token: + logger.error(f"Unable to retrieve access token for page ID: {page_id}") return None - # ─── Helpers: fetching post details ──────────────────────────────────────── + # Fetch post details (description, parent_id) + post_details = self.get_post_details(post_id, page_access_token) + if not post_details: + logger.error(f"Failed to retrieve post details for post ID: {post_id}") + return None - def _fetch_post_details(self, post_id, token): - """ - Returns dict with keys: - - description - - parent_id - - from_id - - parent_from_id - """ + description = post_details.get("description", "") + parent_id = post_details.get("parent_id", None) + author_page_id = post_details.get("parent_from_id", None) + + # Fetch the appropriate OpenAI assistant for the page try: - # first: get this post - fields = "attachments.limit(1){description},parent_id,from" - url1 = f"{self.base_url}/{post_id}?fields={fields}&access_token={token}" - d1 = requests.get(url1).json() + page_assistant = FacebookPageAssistant.objects.get(page_id=page_id) + openai_assistant_model = page_assistant.assistant + logger.info(f"Using assistant '{openai_assistant_model.name}' for page '{page_assistant.page_name}'") + except ObjectDoesNotExist: + logger.error(f"No assistant configured for page ID: {page_id}") + return None - desc = d1.get("attachments", {}).get("data", [{}])[0].get("description", "") - parent = d1.get("parent_id") - from_id = d1.get("from", {}).get("id") + # Generate a meaningful comment based on available data + if not message or message.strip() == "": + if description: + prompt = ( + f"Dr. Dr. Ekaropolus previously said: '{description}'. " + "Based on this, write an insightful response in the most appropriate language that engages people in scientific discussion." + ) + else: + prompt = "Say something truly inspiring about science, a fact or idea that will amaze people." - # then: if shared, get parent author - parent_from = None - if parent: - url2 = f"{self.base_url}/{parent}?fields=from&access_token={token}" - pd = requests.get(url2).json() - parent_from = pd.get("from", {}).get("id") + else: + if description: + prompt = ( + f"Dr. Dr. Ekaropolus previously said: '{message}', " + f"and the shared post describes: '{description}'. " + "Combine these thoughts into an engaging, fun, and insightful response in the most appropriate language." + ) + else: + prompt = f"Dr. Dr. Ekaropolus said: '{message}'. Expand on this with an insightful scientific thought." + + openai_service = OpenAIService(name=openai_assistant_model.name) + bot_response = openai_service.handle_message(prompt) + + sender_id = author_page_id + # Post a comment on the shared post + shared_comment_response = self._post_facebook_comment(post_id, bot_response, page_access_token, sender_id) + + # If the comment on the shared post was successful, store in Neo4j + if shared_comment_response: + self.neo4j_db.store_interaction( + user_id=f"fb_bot_{page_id}", + bot_id=f"fb_bot_{page_id}", + user_message=description if description else "Shared post comment", + bot_response=bot_response, + platform="Facebook" + ) + + # If parent_id exists and the first comment was successful, post the same comment on the original post + if parent_id and shared_comment_response: + logger.info(f"Also commenting on the original post: {parent_id}") + original_comment_response = self._post_facebook_comment(parent_id, bot_response, page_access_token) + + # If the comment on the original post was successful, store in Neo4j + if original_comment_response: + self.neo4j_db.store_interaction( + user_id=f"fb_bot_{page_id}", + bot_id=f"fb_bot_{page_id}", + user_message=description if description else "Original post comment", + bot_response=bot_response, + platform="Facebook (Original Post)" + ) + + return shared_comment_response + + + def get_post_details(self, post_id, access_token): + """ + Retrieves details of a post, including: + - description (from attachments) + - parent_id (if it’s a share) + - from_id (author of THIS post) + - parent_from_id (author of the ORIGINAL post, if shared) + """ + # 1st call: get this post’s description, parent_id, and its own from() + url = ( + f"{self.base_url}/{post_id}" + "?fields=attachments.limit(10){description,media,media_type,target,url}," + "parent_id,from" + f"&access_token={access_token}" + ) + try: + response = requests.get(url) + response.raise_for_status() + data = response.json() + + attachments = data.get("attachments", {}).get("data", [{}]) + description = attachments[0].get("description", "") if attachments else "" + parent_id = data.get("parent_id") + from_id = data.get("from", {}).get("id") + + # default if there is no parent + parent_from_id = None + + # If this is a share, fetch the ORIGINAL post’s author + if parent_id: + parent_url = ( + f"{self.base_url}/{parent_id}" + "?fields=from" + f"&access_token={access_token}" + ) + p_resp = requests.get(parent_url) + p_resp.raise_for_status() + p_data = p_resp.json() + parent_from_id = p_data.get("from", {}).get("id") return { - "description": desc, - "parent_id": parent, - "from_id": from_id, - "parent_from_id": parent_from, + "description": description, + "parent_id": parent_id, + "from_id": from_id, + "parent_from_id": parent_from_id } - except Exception as e: - logger.error(f"Error fetching post details ({post_id}): {e}") + + except requests.exceptions.RequestException as e: + logger.error(f"Failed to fetch post details for {post_id}. Error: {e}") return {} - # ─── Helpers: AI generation ──────────────────────────────────────────────── - def _build_share_prompt(self, incoming, description): - if not incoming.strip(): - return (f"Dr. Dr. Ekaropolus said: '{description}'. " - "Write an insightful scientific comment.") if description \ - else "Share an inspiring science fact." - if description: - return (f"Dr. Dr. Ekaropolus said: '{incoming}', and the post describes: " - f"'{description}'. Combine these thoughts into an engaging reply.") - return f"Expand on: '{incoming}' with an insightful scientific thought." - def _generate_ai_reply(self, assistant_name, prompt): + def _post_facebook_comment(self, post_id, message, access_token, sender_id=None): + """ + Helper function to post a comment to a specific post. + """ + # Prepend mention if sender_id provided + if sender_id: + mention = f"https://www.facebook.com/{sender_id} " + message = f"{mention}{message}{post_id}" + + url = f"{self.base_url}/{post_id}/comments" + payload = {"message": message, "access_token": access_token} try: - svc = OpenAIService(name=assistant_name) - return svc.handle_message(prompt) - except Exception as e: - logger.error(f"OpenAI error: {e}") - return "" - - # ─── Helpers: posting & logging ──────────────────────────────────────────── - - def _prepend_mention(self, message, sender_id): - if not sender_id: - return message - # for fun we keep both link + plain text mention - link = f"https://www.facebook.com/{sender_id}" - tag = f"@[{sender_id}:0]" - return f"{link} {tag} {message}" - - def _post_comment(self, object_id, message, token): - try: - url = f"{self.base_url}/{object_id}/comments" - resp = requests.post(url, data={"message": message, "access_token": token}) - resp.raise_for_status() - logger.info(f"Comment posted on {object_id}") - return resp.json() - except Exception as e: - logger.error(f"Failed posting comment on {object_id}: {e}") + response = requests.post(url, data=payload) + response.raise_for_status() + logger.info(f"Posted a comment on post ID: {post_id}") + return response.json() + except requests.exceptions.RequestException as e: + logger.error(f"Failed to comment on post ID: {post_id}. Error: {e}") return None - def _log_interaction(self, page_id, user_msg, bot_msg, suffix=""): + + def reply_to_comment(self, page_id, comment_id, message): """ - Logs with user_id=fb_bot_{page_id} (or fb_user_), bot_id=fb_bot_{page_id}. + Replies to a specific comment using the Facebook API and OpenAI Assistant. """ + # Retrieve the Page Access Token dynamically + page_access_token = self.get_page_access_token(page_id) + if not page_access_token: + logger.error(f"Unable to retrieve access token for page ID: {page_id}") + return None + + # Fetch the appropriate OpenAI assistant for the page try: + page_assistant = FacebookPageAssistant.objects.get(page_id=page_id) + openai_assistant_model = page_assistant.assistant + logger.info(f"Using assistant '{openai_assistant_model.name}' for page '{page_assistant.page_name}'") + except ObjectDoesNotExist: + logger.error(f"No assistant configured for page ID: {page_id}") + return None + # Use a default message if the received message is empty + if not message or message.strip() == "": + message = "Thank you for sharing this comment! What do you think about it?" + + openai_service = OpenAIService(name=openai_assistant_model.name) # Pass the model's name to the service + bot_response = openai_service.handle_message(message) + + # Send the response to Facebook + url = f"{self.base_url}/{comment_id}/comments" + payload = {"message": bot_response, "access_token": page_access_token} + try: + response = requests.post(url, data=payload) + response.raise_for_status() + logger.info(f"Replied to comment ID: {comment_id}") + # Store the interaction in Neo4j self.neo4j_db.store_interaction( - user_id = f"fb_bot_{page_id}", - bot_id = f"fb_bot_{page_id}", - user_message = user_msg, - bot_response = bot_msg, - platform = f"Facebook{suffix}" + user_id=f"fb_user_{comment_id}", + bot_id=f"fb_bot_{page_id}", + user_message=message, + bot_response=bot_response, + platform="Facebook" ) - except Exception as e: - logger.error(f"Neo4j log error: {e}") + return response.json() + except requests.exceptions.RequestException as e: + logger.error(f"Failed to reply to comment ID: {comment_id}. Error: {e}") + return None