"""BaseDirectTubeScraper — kontrakt dla bezpośrednich scraperów tube'ów.""" from __future__ import annotations import abc from collections.abc import Iterator from app.connectors.base import RawScene class BaseDirectTubeScraper(abc.ABC): """Kontrakt direct scrapera. Wszystkie scrapery feedują do `Source(name=SCRAPER_SOURCE_NAME)` ("tube-scraper", rename z "pornapp" 2026-06-07) żeby dziedziczyć logikę resolvera + idempotent merge per external_id.""" sitetag: str """Stabilny ID tube'a — używany w external_id `f"{sitetag}:{url}"`. Zgodny z porn-app sitetag (hqpornercom, sxylandcom, itp.).""" @abc.abstractmethod def search( self, query: str, *, page: int = 1, limit: int | None = None, ) -> Iterator[RawScene]: """Search tube po query (zwykle: nazwa performera). Yield RawScene per wynik.""" raise NotImplementedError