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:
@@ -62,6 +62,42 @@ DEFAULT_CONFIG: dict = {
|
||||
"link": "a.short-poster",
|
||||
"image": "img",
|
||||
},
|
||||
# Parcours par genre (page Explorer) : type → clé → {path, label}.
|
||||
# Chemins vérifiés en live : films = /films/<genre>/, séries = /<genre>-series-/.
|
||||
"browse": {
|
||||
"serie": {
|
||||
"aventure": {"path": "/aventure-series-/", "label": "Aventure"},
|
||||
"familles": {"path": "/familles-series-/", "label": "Famille"},
|
||||
"fantastique": {"path": "/fantastique-series-/", "label": "Fantastique"},
|
||||
"judiciaire": {"path": "/judiciare-series-/", "label": "Judiciaire"}, # coquille du site
|
||||
"medical": {"path": "/medical-series-/", "label": "Médical"},
|
||||
"romance": {"path": "/romance-series-/", "label": "Romance"},
|
||||
"science-fiction": {"path": "/science-fiction-series-/", "label": "Science-Fiction"},
|
||||
"thriller": {"path": "/thriller-series-/", "label": "Thriller"},
|
||||
"western": {"path": "/western-series-/", "label": "Western"},
|
||||
},
|
||||
"film": {
|
||||
"actions": {"path": "/films/actions/", "label": "Action"},
|
||||
"animations": {"path": "/films/animations/", "label": "Animation"},
|
||||
"aventures": {"path": "/films/aventures/", "label": "Aventure"},
|
||||
"biopics": {"path": "/films/biopics/", "label": "Biopic"},
|
||||
"comedies": {"path": "/films/comedies/", "label": "Comédie"},
|
||||
"cultes": {"path": "/films/cultes/", "label": "Culte"},
|
||||
"documentaires": {"path": "/films/documentaires/", "label": "Documentaire"},
|
||||
"drames": {"path": "/films/drames/", "label": "Drame"},
|
||||
"epouvante-horreurs": {"path": "/films/epouvante-horreurs/", "label": "Épouvante-Horreur"},
|
||||
"espionnages": {"path": "/films/espionnages/", "label": "Espionnage"},
|
||||
"familles": {"path": "/films/familles/", "label": "Famille"},
|
||||
"fantastiques": {"path": "/films/fantastiques/", "label": "Fantastique"},
|
||||
"guerres": {"path": "/films/guerres/", "label": "Guerre"},
|
||||
"historiques": {"path": "/films/historiques/", "label": "Historique"},
|
||||
"policiers": {"path": "/films/policiers/", "label": "Policier"},
|
||||
"romances": {"path": "/films/romances/", "label": "Romance"},
|
||||
"science-fictions": {"path": "/films/science-fictions/", "label": "Science-Fiction"},
|
||||
"thrillers": {"path": "/films/thrillers/", "label": "Thriller"},
|
||||
"westerns": {"path": "/films/westerns/", "label": "Western"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_NEWSID_RE = re.compile(r"(?:newsid=|/)(\d+)(?:-[^/]*)?\.?html?$|(?:newsid=)(\d+)")
|
||||
@@ -158,8 +194,31 @@ class FrenchStreamScraper(SourceScraper):
|
||||
async def latest(self) -> list[SearchResult]:
|
||||
"""Ajouts récents — page /series/ (mix films & séries)."""
|
||||
config = _merged_config()
|
||||
soup = await fetch_soup(f"{self.base_url}{config['latest']['path']}")
|
||||
results = self._short_blocks(soup)
|
||||
logger.info("french_stream : %d nouveautés récupérées", len(results))
|
||||
return results
|
||||
|
||||
async def browse(self, media_type: str, category: str) -> list[SearchResult]:
|
||||
"""Parcours par genre — pages /films/<genre>/ et /<genre>-series-/.
|
||||
|
||||
``media_type`` (« serie » | « film ») et ``category`` (clé du catalogue
|
||||
YAML, ex. « thriller »). Le type est forcé sur les résultats : une page
|
||||
genre séries ne liste que des séries, une page genre films que des films.
|
||||
"""
|
||||
catalog = _merged_config().get("browse", {}).get(media_type, {})
|
||||
entry = catalog.get(category)
|
||||
if entry is None:
|
||||
raise ScrapeError(f"Catégorie inconnue : {media_type}/{category}")
|
||||
soup = await fetch_soup(f"{self.base_url}{entry['path']}")
|
||||
results = self._short_blocks(soup, force_type=media_type)
|
||||
logger.info("french_stream : %d titres dans %s/%s", len(results), media_type, category)
|
||||
return results
|
||||
|
||||
def _short_blocks(self, soup: BeautifulSoup, force_type: str | None = None) -> list[SearchResult]:
|
||||
"""Bloc `div.short` → SearchResult (structure partagée nouveautés/genres)."""
|
||||
config = _merged_config()
|
||||
latest_cfg = config["latest"]
|
||||
soup = await fetch_soup(f"{self.base_url}{latest_cfg['path']}")
|
||||
results: list[SearchResult] = []
|
||||
for block in soup.select(latest_cfg["item"]):
|
||||
link = block.select_one(latest_cfg["link"])
|
||||
@@ -179,12 +238,19 @@ class FrenchStreamScraper(SourceScraper):
|
||||
title=title,
|
||||
url=f"{self.base_url}{href}" if href.startswith("/") else href,
|
||||
image_url=image.get("src") if image else None,
|
||||
media_type=_media_type(title, href),
|
||||
media_type=force_type or _media_type(title, href),
|
||||
)
|
||||
)
|
||||
logger.info("french_stream : %d nouveautés récupérées", len(results))
|
||||
return results
|
||||
|
||||
def browse_catalog(self) -> dict[str, dict[str, str]]:
|
||||
"""Catalogue des genres parcourables : ``{type: {clé: libellé}}``."""
|
||||
catalog = _merged_config().get("browse", {})
|
||||
return {
|
||||
media_type: {key: entry.get("label", key) for key, entry in genres.items()}
|
||||
for media_type, genres in catalog.items()
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------- details
|
||||
|
||||
async def get_details(self, source_id: str) -> TitleDetails:
|
||||
|
||||
Reference in New Issue
Block a user