"""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 core.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("core: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