""" Content negotiation utilities. Helps determine what response format the client wants. """ from enum import Enum from typing import Optional from fastapi import Request class ContentType(Enum): """Response content types.""" HTML = "text/html" JSON = "application/json" ACTIVITY_JSON = "application/activity+json" XML = "application/xml" def wants_html(request: Request) -> bool: """ Check if the client wants HTML response. Returns True if: - Accept header contains text/html - Accept header contains application/xhtml+xml - No Accept header (browser default) Args: request: FastAPI request Returns: True if HTML is preferred """ accept = request.headers.get("accept", "") # No accept header usually means browser if not accept: return True # Check for HTML preferences if "text/html" in accept: return True if "application/xhtml" in accept: return True return False def wants_json(request: Request) -> bool: """ Check if the client wants JSON response. Returns True if: - Accept header contains application/json - Accept header does NOT contain text/html - Request has .json suffix (convention) Args: request: FastAPI request Returns: True if JSON is preferred """ accept = request.headers.get("accept", "") # Explicit JSON preference if "application/json" in accept: # But not if HTML is also requested (browsers often send both) if "text/html" not in accept: return True # Check URL suffix convention if request.url.path.endswith(".json"): return True return False def wants_activity_json(request: Request) -> bool: """ Check if the client wants ActivityPub JSON-LD response. Used for federation with other ActivityPub servers. Args: request: FastAPI request Returns: True if ActivityPub format is preferred """ accept = request.headers.get("accept", "") if "application/activity+json" in accept: return True if "application/ld+json" in accept: return True return False def get_preferred_type(request: Request) -> ContentType: """ Determine the preferred content type from Accept header. Args: request: FastAPI request Returns: ContentType enum value """ if wants_activity_json(request): return ContentType.ACTIVITY_JSON if wants_json(request): return ContentType.JSON return ContentType.HTML def is_htmx_request(request: Request) -> bool: """ Check if this is an HTMX request (partial page update). HTMX requests set the HX-Request header. Args: request: FastAPI request Returns: True if this is an HTMX request """ return request.headers.get("HX-Request") == "true" def get_htmx_target(request: Request) -> Optional[str]: """ Get the HTMX target element ID. Args: request: FastAPI request Returns: Target element ID or None """ return request.headers.get("HX-Target") def get_htmx_trigger(request: Request) -> Optional[str]: """ Get the HTMX trigger element ID. Args: request: FastAPI request Returns: Trigger element ID or None """ return request.headers.get("HX-Trigger") def is_ios_request(request: Request) -> bool: """ Check if request is from iOS device. Useful for video format selection (iOS prefers MP4). Args: request: FastAPI request Returns: True if iOS user agent detected """ user_agent = request.headers.get("user-agent", "").lower() return "iphone" in user_agent or "ipad" in user_agent