197 lines
7.8 KiB
Python
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}")
|