Connect Facebook and Instagram via shared portal Meta App credentials (Facebook Login for Business), and let compose attach DB-stored images/videos with live preview and platform publish.
92 lines
3.0 KiB
Python
92 lines
3.0 KiB
Python
"""Helpers for social post media (StoredFile-backed)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from typing import Any
|
|
|
|
from django.conf import settings
|
|
from django.urls import reverse
|
|
|
|
from messaging.models import StoredFile
|
|
|
|
ALLOWED_IMAGE_TYPES = frozenset(
|
|
{"image/jpeg", "image/jpg", "image/png", "image/gif", "image/webp"}
|
|
)
|
|
ALLOWED_VIDEO_TYPES = frozenset(
|
|
{"video/mp4", "video/quicktime", "video/webm"}
|
|
)
|
|
MAX_IMAGE_BYTES = 8 * 1024 * 1024 # 8 MB
|
|
MAX_VIDEO_BYTES = 100 * 1024 * 1024 # 100 MB
|
|
MAX_IMAGES = 10
|
|
|
|
|
|
def public_file_url(file_id: str) -> str:
|
|
"""Absolute URL Meta/LinkedIn can fetch (UUID = capability token)."""
|
|
base = (settings.PUBLIC_SITE_URL or "").rstrip("/")
|
|
path = reverse("messaging:stored_file", kwargs={"pk": file_id})
|
|
if not base:
|
|
return path
|
|
return f"{base}{path}"
|
|
|
|
|
|
def media_item_from_stored(stored: StoredFile) -> dict[str, Any]:
|
|
kind = stored.kind
|
|
media_type = "video" if kind == StoredFile.Kind.SOCIAL_VIDEO else "image"
|
|
return {
|
|
"id": str(stored.pk),
|
|
"type": media_type,
|
|
"url": public_file_url(str(stored.pk)),
|
|
"content_type": stored.content_type,
|
|
"filename": stored.filename or "",
|
|
"size": stored.size,
|
|
}
|
|
|
|
|
|
def parse_media_json(raw: str) -> list[dict[str, Any]]:
|
|
"""Parse composer hidden media JSON into a cleaned list."""
|
|
raw = (raw or "").strip()
|
|
if not raw:
|
|
return []
|
|
try:
|
|
data = json.loads(raw)
|
|
except json.JSONDecodeError as exc:
|
|
raise ValueError("Invalid media payload.") from exc
|
|
if not isinstance(data, list):
|
|
raise ValueError("Media payload must be a list.")
|
|
|
|
items: list[dict[str, Any]] = []
|
|
for entry in data:
|
|
if not isinstance(entry, dict):
|
|
continue
|
|
file_id = str(entry.get("id") or "").strip()
|
|
if not file_id:
|
|
continue
|
|
try:
|
|
stored = StoredFile.objects.get(pk=file_id)
|
|
except (StoredFile.DoesNotExist, ValueError) as exc:
|
|
raise ValueError(f"Unknown media file: {file_id}") from exc
|
|
if stored.kind not in {
|
|
StoredFile.Kind.SOCIAL_IMAGE,
|
|
StoredFile.Kind.SOCIAL_VIDEO,
|
|
StoredFile.Kind.CAMPAIGN_IMAGE,
|
|
}:
|
|
raise ValueError("File is not a social media attachment.")
|
|
items.append(media_item_from_stored(stored))
|
|
|
|
videos = [m for m in items if m["type"] == "video"]
|
|
images = [m for m in items if m["type"] == "image"]
|
|
if len(videos) > 1:
|
|
raise ValueError("Attach at most one video per post.")
|
|
if videos and images:
|
|
raise ValueError("Use either images or one video — not both.")
|
|
if len(images) > MAX_IMAGES:
|
|
raise ValueError(f"Attach at most {MAX_IMAGES} images.")
|
|
return items
|
|
|
|
|
|
def split_media(media: list[dict[str, Any]]) -> tuple[list[dict], list[dict]]:
|
|
images = [m for m in media if m.get("type") == "image"]
|
|
videos = [m for m in media if m.get("type") == "video"]
|
|
return images, videos
|