Admin for Suscribing to Messenger Servers
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
c954488c28
commit
212ce3b129
@ -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 you’ll 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",)
|
||||
|
Loading…
x
Reference in New Issue
Block a user