Admin for Suscribing to Messenger Servers
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Ekaropolus 2025-09-07 04:16:17 -06:00
parent c954488c28
commit 212ce3b129

View File

@ -1,24 +1,71 @@
import json
import requests
from django.conf import settings
from django.contrib import admin
from .models import FacebookPageAssistant
from .services import FacebookService # Import FacebookService for API calls
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 panel configuration for managing Facebook Page Assistants.
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"
"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 = ["subscribe_to_webhook", "check_subscription_status"]
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"
@ -27,79 +74,223 @@ class FacebookPageAssistantAdmin(admin.ModelAdmin):
return obj.events.filter(event_type__code="share").count()
share_count.short_description = "Shares"
def subscribe_to_webhook(self, request, queryset):
# =====================================================================
# ACTION 1: Ensure required fields are subscribed (feed + Messenger)
# =====================================================================
def ensure_feed_and_messenger_subscription(self, request, queryset):
"""
Subscribes selected pages to Facebook webhooks using FacebookService.
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:
page_access_token = fb_service.get_page_access_token(page.page_id)
if not page_access_token:
self.message_user(request, f"Failed to get access token for {page.page_name}", level="error")
continue
url = f"https://graph.facebook.com/v22.0/{page.page_id}/subscribed_apps"
data = {
"subscribed_fields": "feed,mention",
"access_token": page_access_token
}
try:
response = requests.post(url, data=data)
response.raise_for_status()
page.is_subscribed = True
page.save()
self.message_user(request, f"Successfully subscribed {page.page_name} to webhooks.")
# 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:
self.message_user(request, f"Failed to subscribe {page.page_name}: {e}", level="error")
# 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,
)
subscribe_to_webhook.short_description = "Subscribe selected pages to webhooks"
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):
"""
Checks whether selected pages are subscribed to Facebook webhooks using FacebookService.
Shows the actual subscribed_fields for each Page.
"""
fb_service = FacebookService(user_access_token=settings.PAGE_ACCESS_TOKEN)
for page in queryset:
page_access_token = fb_service.get_page_access_token(page.page_id)
if not page_access_token:
self.message_user(request, f"Failed to get access token for {page.page_name}", level="error")
continue
url = f"https://graph.facebook.com/v22.0/{page.page_id}/subscribed_apps?access_token={page_access_token}"
try:
response = requests.get(url)
response.raise_for_status()
data = response.json()
if "data" in data and len(data["data"]) > 0:
page.is_subscribed = True
self.message_user(request, f"{page.page_name} is subscribed.")
# 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.is_subscribed = False
self.message_user(request, f"{page.page_name} is NOT subscribed.", level="warning")
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,
)
page.save()
except requests.RequestException as e:
self.message_user(request, f"Failed to check subscription for {page.page_name}: {e}", level="error")
self.message_user(
request, f"[{page.page_name}] Check failed: {e}", level=messages.ERROR
)
check_subscription_status.short_description = "Check webhook subscription status"
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"
from .models import EventType
@admin.register(EventType)
class EventTypeAdmin(admin.ModelAdmin):
list_display = ("code", "label")
search_fields = ("code", "label")
from .models import BotInteraction
@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",)
list_display = ("page", "object_id", "parent_object_id", "platform", "created_at")
search_fields = ("object_id", "prompt", "bot_response")
list_filter = ("platform",)