197 lines
7.8 KiB
Python

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 responses, 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()
# ─── Public API ─────────────────────────────────────────────────────────────
def post_comment_on_share(self, page_id, post_id, incoming_msg="", sender_id=None):
"""
Orchestrates commenting on a shared post, then on the original.
"""
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 None
def _get_assistant_name(self, page_id):
try:
return FacebookPageAssistant.objects.get(page_id=page_id).assistant.name
except ObjectDoesNotExist:
logger.error(f"No OpenAI assistant configured for page {page_id}")
return None
# ─── Helpers: fetching post details ────────────────────────────────────────
def _fetch_post_details(self, post_id, token):
"""
Returns dict with keys:
- description
- parent_id
- from_id
- parent_from_id
"""
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()
desc = d1.get("attachments", {}).get("data", [{}])[0].get("description", "")
parent = d1.get("parent_id")
from_id = d1.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")
return {
"description": desc,
"parent_id": parent,
"from_id": from_id,
"parent_from_id": parent_from,
}
except Exception as e:
logger.error(f"Error fetching post details ({post_id}): {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):
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}")
return None
def _log_interaction(self, page_id, user_msg, bot_msg, suffix=""):
"""
Logs with user_id=fb_bot_{page_id} (or fb_user_), bot_id=fb_bot_{page_id}.
"""
try:
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}"
)
except Exception as e:
logger.error(f"Neo4j log error: {e}")