Adding post id to debbug
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Ekaropolus 2025-07-22 13:38:08 -06:00
parent fbfcf3e643
commit 7290e575f6

View File

@ -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
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
# 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
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:
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 OpenAI assistant configured for page {page_id}")
logger.error(f"No assistant configured for page ID: {page_id}")
return None
# ─── Helpers: fetching post details ────────────────────────────────────────
# 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."
def _fetch_post_details(self, post_id, token):
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):
"""
Returns dict with keys:
- description
- parent_id
- from_id
- parent_from_id
Retrieves details of a post, including:
- description (from attachments)
- parent_id (if its a share)
- from_id (author of THIS post)
- parent_from_id (author of the ORIGINAL post, if shared)
"""
# 1st call: get this posts 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:
# 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()
response = requests.get(url)
response.raise_for_status()
data = response.json()
desc = d1.get("attachments", {}).get("data", [{}])[0].get("description", "")
parent = d1.get("parent_id")
from_id = d1.get("from", {}).get("id")
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")
# 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")
# default if there is no parent
parent_from_id = None
# If this is a share, fetch the ORIGINAL posts 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,
"description": description,
"parent_id": parent_id,
"from_id": from_id,
"parent_from_id": parent_from,
"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