"""Découverte : nouveautés par type, incontournables, recommandations, exploration.""" import logging from typing import Annotated from fastapi import APIRouter, Depends, Query from app.routers.auth import CurrentUser, current_user from app.routers.search import _allowed_media_types from app.scrapers.base import ScrapeError, get_source from app.services.discover import ANIME_GENRES, discover logger = logging.getLogger(__name__) router = APIRouter(prefix="/api", tags=["discover"], dependencies=[Depends(current_user)]) @router.get("/discover") async def get_discover( user: CurrentUser, latest_limit: Annotated[int, Query(ge=1, le=50)] = 24, must_watch_limit: Annotated[int, Query(ge=1, le=20)] = 20, for_you_limit: Annotated[int, Query(ge=1, le=20)] = 20, ) -> dict: """Rails de découverte en une requête (sections vides si source KO). « latest_anime » et « latest_serie » (séries + films réels) sont des rails indépendants, filtrés par la préférence de contenu du compte. """ allowed = _allowed_media_types(user.content_preference) rails = await discover.latest_by_type(latest_limit) latest_anime = rails["anime"] if "anime" in allowed else [] latest_serie = (rails["serie"] + rails["film"]) if {"serie", "film"} & allowed else [] # Incontournables et Pour toi sont issus de Kitsu (catalogue animés) : # en mode séries, elles n'ont pas de sens — on ne les calcule même pas. if "anime" in allowed: must_watch = await discover.must_watch(must_watch_limit) for_you = await discover.for_you(user.id, for_you_limit) else: must_watch = [] for_you = {"based_on": [], "items": []} return { "latest_anime": latest_anime, "latest_serie": latest_serie, "must_watch": must_watch, "for_you": for_you, } @router.get("/discover/genres") async def get_genres(user: CurrentUser) -> dict: """Catalogue des genres parcourables (page Explorer), selon la préférence.""" allowed = _allowed_media_types(user.content_preference) catalog: dict[str, list[dict]] = {} if "anime" in allowed: catalog["anime"] = [{"key": key, "label": label} for key, label in ANIME_GENRES.items()] try: french_stream = get_source("french_stream") available = french_stream.browse_catalog() except ScrapeError: available = {} for kind in ("serie", "film"): if kind in allowed and kind in available: catalog[kind] = [ {"key": key, "label": label} for key, label in available[kind].items() ] return catalog @router.get("/discover/browse") async def get_browse( user: CurrentUser, type: Annotated[str, Query(pattern="^(anime|serie|film)$")], genre: Annotated[str, Query(min_length=1, max_length=40)], limit: Annotated[int, Query(ge=1, le=40)] = 24, ) -> dict: """Titres d'un genre : animés via Kitsu, séries/films via French-Stream.""" if type not in _allowed_media_types(user.content_preference): return {"items": []} # hors préférence du compte if type == "anime": return {"items": await discover.browse_anime(genre, limit)} return {"items": await discover.browse_serie_film(type, genre, limit)}