Ekaropolus 212ce3b129
All checks were successful
continuous-integration/drone/push Build is passing
Admin for Suscribing to Messenger Servers
2025-09-07 04:16:17 -06:00

297 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import json
import requests
from django.conf import settings
from django.contrib import admin, messages
from .models import FacebookPageAssistant, EventType, BotInteraction
from .services import FacebookService
# Required fields we want on every Page
REQUIRED_FIELDS = [
# Page feed (comments/shares/mentions)
"feed",
"mention",
# Messenger
"messages",
"messaging_postbacks",
"message_reads",
"message_deliveries",
"message_reactions",
"message_echoes",
]
APP_ID = getattr(settings, "FACEBOOK_APP_ID", None) # optional (nice-to-have for filtering)
def _graph_get(url, params):
r = requests.get(url, params=params, timeout=15)
# Graph often returns 200 even for failures with {"error":{...}}
data = r.json() if r.content else {}
if "error" in data:
raise requests.RequestException(json.dumps(data["error"]))
r.raise_for_status()
return data
def _graph_post(url, data):
r = requests.post(url, data=data, timeout=15)
data = r.json() if r.content else {}
if "error" in data:
raise requests.RequestException(json.dumps(data["error"]))
r.raise_for_status()
return data
@admin.register(FacebookPageAssistant)
class FacebookPageAssistantAdmin(admin.ModelAdmin):
"""
Admin for wiring a Facebook Page to your assistant and managing webhook subs.
"""
list_display = (
"page_name",
"page_id",
"assistant",
"is_subscribed",
"created_at",
"comment_count",
"share_count",
)
search_fields = ("page_name", "page_id", "assistant__name")
list_filter = ("is_subscribed", "assistant")
actions = [
"ensure_feed_and_messenger_subscription",
"check_subscription_status",
"probe_messenger_access",
]
# ----- small counters -----
def comment_count(self, obj):
return obj.events.filter(event_type__code="comment").count()
comment_count.short_description = "Comments"
def share_count(self, obj):
return obj.events.filter(event_type__code="share").count()
share_count.short_description = "Shares"
# =====================================================================
# ACTION 1: Ensure required fields are subscribed (feed + Messenger)
# =====================================================================
def ensure_feed_and_messenger_subscription(self, request, queryset):
"""
For each selected Page:
- fetch Page Access Token with FacebookService
- read current subscribed_fields
- add any missing REQUIRED_FIELDS
"""
fb_service = FacebookService(user_access_token=settings.PAGE_ACCESS_TOKEN)
for page in queryset:
try:
# 1) token
page_token = getattr(fb_service, "get_page_access_token", None)
if callable(page_token):
page_access_token = page_token(page.page_id)
else:
# fallback to private method name in case your svc only exposes _get_page_access_token
page_access_token = fb_service._get_page_access_token(page.page_id) # noqa
if not page_access_token:
self.message_user(
request,
f"[{page.page_name}] Unable to get Page Access Token.",
level=messages.ERROR,
)
continue
# 2) read existing
url_list = f"https://graph.facebook.com/v22.0/{page.page_id}/subscribed_apps"
data = _graph_get(url_list, {"access_token": page_access_token}) or {}
entries = data.get("data", [])
# pick this app's entry (if APP_ID known), else first entry if any
app_entry = None
if APP_ID:
app_entry = next((e for e in entries if str(e.get("id")) == str(APP_ID)), None)
if app_entry is None and entries:
app_entry = entries[0]
current = set(app_entry.get("subscribed_fields", [])) if app_entry else set()
required = set(REQUIRED_FIELDS)
union_fields = sorted(current | required)
# 3) update only if needed
if required - current:
_graph_post(
f"https://graph.facebook.com/v22.0/{page.page_id}/subscribed_apps",
{
"subscribed_fields": ",".join(union_fields),
"access_token": page_access_token,
},
)
page.is_subscribed = True
page.save(update_fields=["is_subscribed"])
self.message_user(
request,
f"[{page.page_name}] Subscribed/updated. Fields now include: {', '.join(union_fields)}",
level=messages.SUCCESS,
)
else:
page.is_subscribed = True
page.save(update_fields=["is_subscribed"])
self.message_user(
request,
f"[{page.page_name}] Already has all required fields: {', '.join(sorted(current))}",
level=messages.INFO,
)
except requests.RequestException as e:
# try to decode Graph error for clarity
msg = str(e)
try:
err = json.loads(msg)
code = err.get("code")
sub = err.get("error_subcode")
err_msg = err.get("message", "Graph error")
self.message_user(
request,
f"[{page.page_name}] Graph error (code={code}, subcode={sub}): {err_msg}",
level=messages.ERROR,
)
except Exception:
self.message_user(
request,
f"[{page.page_name}] Subscription failed: {msg}",
level=messages.ERROR,
)
except Exception as e:
self.message_user(
request, f"[{page.page_name}] Unexpected error: {e}", level=messages.ERROR
)
ensure_feed_and_messenger_subscription.short_description = "Ensure Webhooks (feed + Messenger) on selected Pages"
# =====================================================================
# ACTION 2: Check status (show exact fields)
# =====================================================================
def check_subscription_status(self, request, queryset):
"""
Shows the actual subscribed_fields for each Page.
"""
fb_service = FacebookService(user_access_token=settings.PAGE_ACCESS_TOKEN)
for page in queryset:
try:
# token
page_token = getattr(fb_service, "get_page_access_token", None)
if callable(page_token):
page_access_token = page_token(page.page_id)
else:
page_access_token = fb_service._get_page_access_token(page.page_id) # noqa
if not page_access_token:
self.message_user(
request,
f"[{page.page_name}] Unable to get Page Access Token.",
level=messages.ERROR,
)
continue
url = f"https://graph.facebook.com/v22.0/{page.page_id}/subscribed_apps"
data = _graph_get(url, {"access_token": page_access_token}) or {}
entries = data.get("data", [])
app_entry = None
if APP_ID:
app_entry = next((e for e in entries if str(e.get("id")) == str(APP_ID)), None)
if app_entry is None and entries:
app_entry = entries[0]
fields = app_entry.get("subscribed_fields", []) if app_entry else []
has_required = set(REQUIRED_FIELDS).issubset(set(fields))
page.is_subscribed = bool(fields)
page.save(update_fields=["is_subscribed"])
level = messages.SUCCESS if has_required else messages.WARNING
self.message_user(
request,
f"[{page.page_name}] Subscribed fields: {', '.join(fields) or '(none)'}",
level=level,
)
except requests.RequestException as e:
self.message_user(
request, f"[{page.page_name}] Check failed: {e}", level=messages.ERROR
)
check_subscription_status.short_description = "Check webhook subscription fields on selected Pages"
# =====================================================================
# ACTION 3: Probe Messenger access (lightweight)
# =====================================================================
def probe_messenger_access(self, request, queryset):
"""
Tries /{PAGE_ID}/conversations to confirm Messenger perms are usable.
(If app is in Dev Mode, only app roles will appear here.)
"""
fb_service = FacebookService(user_access_token=settings.PAGE_ACCESS_TOKEN)
for page in queryset:
try:
page_token = getattr(fb_service, "get_page_access_token", None)
if callable(page_token):
page_access_token = page_token(page.page_id)
else:
page_access_token = fb_service._get_page_access_token(page.page_id) # noqa
if not page_access_token:
self.message_user(
request,
f"[{page.page_name}] Unable to get Page Access Token.",
level=messages.ERROR,
)
continue
url = f"https://graph.facebook.com/v22.0/{page.page_id}/conversations"
data = _graph_get(url, {"access_token": page_access_token, "limit": 1})
total = len(data.get("data", []))
self.message_user(
request,
f"[{page.page_name}] Messenger probe OK. Conversations sample: {total}. "
"Note: in Dev Mode youll only see app-role users here.",
level=messages.SUCCESS,
)
except requests.RequestException as e:
# common Graph codes for perms/token issues:
# 190 invalid/expired token, 200 permissions error, 10 permission denied
msg = str(e)
hint = ""
if any(x in msg for x in ('"code": 190', "Invalid OAuth 2.0")):
hint = " (Token invalid/expired)"
elif '"code": 200' in msg:
hint = " (Permissions error: check pages_messaging & pages_manage_metadata; app roles or Advanced Access)"
elif '"code": 10' in msg:
hint = " (Permission denied: user role or access level missing)"
self.message_user(
request,
f"[{page.page_name}] Messenger probe failed: {msg}{hint}",
level=messages.ERROR,
)
probe_messenger_access.short_description = "Probe Messenger access on selected Pages"
@admin.register(EventType)
class EventTypeAdmin(admin.ModelAdmin):
list_display = ("code", "label")
search_fields = ("code", "label")
@admin.register(BotInteraction)
class BotInteractionAdmin(admin.ModelAdmin):
list_display = ("page", "object_id", "parent_object_id", "platform", "created_at")
search_fields = ("object_id", "prompt", "bot_response")
list_filter = ("platform",)