Découvrir v2 : rails par type, exploration par genre
- Rails Nouveautés séparés : 🆕 animés (tri date Kitsu) et 🆕 séries & films (French-Stream) — chaque type garde son rail, plus d'écrasement mutuel - Section 🎭 Explorer : chips type × genre — animés via catégories Kitsu, séries (/<genre>-series-/) et films (/films/<genre>/) via French-Stream ; état dans l'URL (/discover?t=serie&g=medical), partageable - Scraper : browse(media_type, genre) + catalogue YAML surchargeable ; parsing div.short mutualisé avec les nouveautés - Films réels retirés du mode « Animés » (ils suivent le rail séries) - Endpoints /api/discover (latest_anime/latest_serie), /api/discover/genres, /api/discover/browse — parcours filtré par la préférence du compte - Warmup démarrage : latest_by_type()
This commit is contained in:
+52
-5
@@ -1,4 +1,4 @@
|
||||
"""Découverte : nouveautés des sources, incontournables, recommandations."""
|
||||
"""Découverte : nouveautés par type, incontournables, recommandations, exploration."""
|
||||
|
||||
import logging
|
||||
from typing import Annotated
|
||||
@@ -7,7 +7,8 @@ from fastapi import APIRouter, Depends, Query
|
||||
|
||||
from app.routers.auth import CurrentUser, current_user
|
||||
from app.routers.search import _allowed_media_types
|
||||
from app.services.discover import discover
|
||||
from app.scrapers.base import ScrapeError, get_source
|
||||
from app.services.discover import ANIME_GENRES, discover
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -21,9 +22,15 @@ async def get_discover(
|
||||
must_watch_limit: Annotated[int, Query(ge=1, le=20)] = 20,
|
||||
for_you_limit: Annotated[int, Query(ge=1, le=20)] = 20,
|
||||
) -> dict:
|
||||
"""Les trois sections de découverte en une requête (sections vides si source KO)."""
|
||||
"""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)
|
||||
latest = await discover.latest(latest_limit, allowed)
|
||||
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:
|
||||
@@ -32,4 +39,44 @@ async def get_discover(
|
||||
else:
|
||||
must_watch = []
|
||||
for_you = {"based_on": [], "items": []}
|
||||
return {"latest": latest, "must_watch": must_watch, "for_you": for_you}
|
||||
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)}
|
||||
|
||||
@@ -27,11 +27,11 @@ router = APIRouter(prefix="/api", tags=["search"], dependencies=[Depends(current
|
||||
import_all_scrapers()
|
||||
kitsu = KitsuService()
|
||||
|
||||
# Types de médias gardés selon la préférence : les films restent avec les animés
|
||||
# (cinéma d'animation), une future source séries les exclurait donc du mode « séries ».
|
||||
# Types de médias gardés selon la préférence. Les films réels (French-Stream)
|
||||
# accompagnent les séries — le mode animés reste sur l'animation.
|
||||
_PREFERENCE_MEDIA_TYPES = {
|
||||
"anime": {"anime", "film"},
|
||||
"serie": {"serie"},
|
||||
"anime": {"anime"},
|
||||
"serie": {"serie", "film"},
|
||||
"both": {"anime", "serie", "film"},
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user