"""Base class for series streaming sites (catalogs)""" from abc import abstractmethod from typing import List, Dict, Any, Optional, Tuple import logging import httpx from bs4 import BeautifulSoup logger = logging.getLogger(__name__) class BaseSeriesSite: """ Base class for series streaming sites. Series sites provide catalogs, metadata, and episode listings. They typically link to video players for actual file hosting. Examples: FS7 (French Stream), etc. KEY FEATURE: Provides rich metadata and episode management for TV series """ def __init__(self): # Initialize HTTP client directly self.client = httpx.AsyncClient(timeout=10.0, follow_redirects=True) @abstractmethod def can_handle(self, url: str) -> bool: """Check if this series site can handle the given URL""" pass @abstractmethod async def search_anime( self, query: str, lang: str = "vf" ) -> List[Dict[str, str]]: """ Search for series on this site. Args: query: Search query (series title) lang: Language preference (vf, vostfr) Returns: List of series with keys: - title: Series title - url: Series page URL - cover_image: Optional cover image URL - lang: Available languages """ pass @abstractmethod async def get_episodes( self, anime_url: str, lang: str = "vf" ) -> List[Dict[str, str]]: """ Get list of episodes for a series. Args: anime_url: URL of the series page lang: Language preference Returns: List of episodes with keys: - episode_number: Episode number - url: Episode page URL - title: Optional episode title - host: Video player hosting the file """ pass @abstractmethod async def get_anime_metadata(self, anime_url: str) -> Dict[str, Any]: """ Get detailed metadata for a series. Args: anime_url: URL of the series page Returns: Dict with metadata: - title: Series title - synopsis: Plot summary - genres: List of genres - rating: Rating (e.g., "8.5/10") - release_year: Release year - studio: Production studio - poster_image: Poster URL - total_episodes: Total episode count - status: Airing status (ongoing, completed) - languages: Available languages """ pass @abstractmethod async def get_download_link(self, url: str) -> Tuple[str, str]: """ Get download link for a specific episode. For series sites, this extracts the video player URL from an episode page. Note: Returns video player URL, NOT direct download link! Returns: Tuple of (video_player_url, episode_title) """ pass # Common methods for all series sites async def close(self): """Close HTTP client""" await self.client.aclose() async def _fetch_page(self, url: str) -> str: """Fetch HTML page content""" response = await self.client.get(url) response.raise_for_status() return response.text def _parse_html(self, html: str) -> BeautifulSoup: """Parse HTML with BeautifulSoup""" return BeautifulSoup(html, 'lxml') def _extract_season_number(self, title: str) -> Optional[int]: """Extract season number from title (e.g., 'Saison 2' -> 2)""" import re match = re.search(r'saison\s*(\d+)', title.lower()) return int(match.group(1)) if match else None