""" Базовые модели данных и Protocol-интерфейс для источников манги. """ from __future__ import annotations from dataclasses import dataclass, field from pathlib import Path from typing import TYPE_CHECKING, Optional, Protocol, runtime_checkable if TYPE_CHECKING: from playwright.async_api import Page class AuthRequiredError(Exception): """Источник требует авторизации — токен не задан или просрочен.""" def __init__(self, source_slug: str): self.source_slug = source_slug super().__init__(f"Auth required for source: {source_slug}") # ────────────────────────────────────────────── # Модели данных (общие для всех источников) # ────────────────────────────────────────────── @dataclass class Chapter: title: str url: str number: float = 0.0 volume: int = 0 @dataclass class MangaInfo: title: str url: str chapters: list[Chapter] = field(default_factory=list) pub_status: str = "unknown" # completed / ongoing / unknown title_ru: str = "" title_full: str = "" description: str = "" genres: list[str] = field(default_factory=list) tags: list[str] = field(default_factory=list) cover_url: str = "" # ────────────────────────────────────────────── # Интерфейс источника # ────────────────────────────────────────────── @runtime_checkable class MangaSourceProtocol(Protocol): slug: str # уникальный код источника в коде ("readmanga") display_name: str # название для UI ("ReadManga") async def get_manga_info(self, page: Page, url: str) -> Optional[MangaInfo]: """Возвращает информацию о манге и список глав.""" ... async def get_chapter_images_and_download( self, page: Page, chapter_url: str, dest_dir: Path, manga_url: Optional[str] = None, on_page: object = None, ) -> list[Path]: """Скачивает страницы главы в dest_dir и возвращает список путей.""" ...