Files
westfarn 30ce58e610
Deploy Beta / unit-tests (push) Successful in 14s
Deploy Beta / docker (push) Successful in 17s
Deploy Beta / deploy-beta (push) Successful in 1m32s
Add Meta Facebook Login OAuth and social media attach/publish.
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.
2026-08-11 14:45:23 -05:00

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