"""Base class for anime 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 BaseAnimeSite: """ Base class for anime streaming sites. Anime sites provide catalogs, metadata, and episode listings. They typically link to video players for actual file hosting. Examples: Anime-Sama, Neko-Sama, Anime-Ultime, Vostfree, etc. KEY FEATURE: Provides rich metadata and episode management """ def __init__(self): # Realistic browser headers to avoid blocking by video hosts headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8", "Accept-Language": "en-US,en;q=0.9,fr;q=0.8", "Referer": "https://anime-sama.tv/", } # Initialize HTTP client with browser headers self.client = httpx.AsyncClient(timeout=10.0, follow_redirects=True, headers=headers) @abstractmethod def can_handle(self, url: str) -> bool: """Check if this anime site can handle the given URL""" pass @abstractmethod async def search_anime( self, query: str, lang: str = "vostfr" ) -> List[Dict[str, str]]: """ Search for anime on this site. Args: query: Search query (anime title) lang: Language preference (vostfr, vf) Returns: List of anime with keys: - title: Anime title - url: Anime page URL - cover_image: Optional cover image URL - lang: Available languages """ pass @abstractmethod async def get_episodes( self, anime_url: str, lang: str = "vostfr" ) -> List[Dict[str, str]]: """ Get list of episodes for an anime. Args: anime_url: URL of the anime 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 an anime. Args: anime_url: URL of the anime page Returns: Dict with metadata: - title: Anime title - synopsis: Plot summary - genres: List of genres - rating: Rating (e.g., "8.5/10") - release_year: Release year - studio: Animation 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 anime 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 anime 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