diff --git a/app/api/sources.py b/app/api/sources.py index f70d9c6..76cef9a 100644 --- a/app/api/sources.py +++ b/app/api/sources.py @@ -110,7 +110,7 @@ class SourceListOut(BaseModel): # tubey mają nietypowe TLDs / brakujące kropki w sitetag. _DISPLAY_OVERRIDES: dict[str, str] = { "fpoxxx": "fpo.xxx", - "siskavideo": "siska.video", + "siskavideo": "siska.tv", "porn4dayspw": "porn4days.pw", "porn00org": "porn00.org", "freshpornoorg": "freshporno.org", diff --git a/app/connectors/direct_scrapers/siska.py b/app/connectors/direct_scrapers/siska.py index f866784..9c06c04 100644 --- a/app/connectors/direct_scrapers/siska.py +++ b/app/connectors/direct_scrapers/siska.py @@ -1,8 +1,8 @@ -"""siska.video, latest-vids browse scraper. +"""siska.tv (do 2026-09-22 siska.video), latest-vids browse scraper. Historia: dawniej performer-driven search scraper (`?s=`), ale siska zepsuła wyszukiwarkę (zwraca latest niezależnie od query). Przerobione na BROWSE (latest -chronologicznie z `/page//`), re-enabled 2026-06-20 (user fa4083a2). +chronologicznie z listingu, patrz `_listing_url`), re-enabled 2026-06-20 (user fa4083a2). Cały blok kafelka listingu ma komplet metadanych (zero detail-fetchy): @@ -36,11 +36,18 @@ from app.extractors import browser_get log = logging.getLogger(__name__) -_BASE = "https://siska.video" +# Domena siska.video wygasla 2026-09-22 (NXDOMAIN), ten sam serwis dziala dalej pod +# siska.tv: te same numery videoID i sciezki miniatur. Adresy w bazie przepisane na +# siska.tv tego samego dnia. Kafelki przyjmujemy z obu domen i z www, ale zapisujemy +# zawsze jeden kanoniczny adres (_canonical), bo external_id sceny i unikalny klucz +# zrodla to sam adres; www.siska.video i siska.video dawaly 103 zdublowane zrodla. +_BASE = "https://siska.tv" +_HOST_RE = re.compile(r"^https://(?:www\.)?siska\.(?:tv|video)/", re.IGNORECASE) # Kafelek: . Reszta pól w oknie po tym matchu. _A_RE = re.compile( - r"[^']*)'\s+href='(?Phttps://siska\.video/video\.php\?videoID=\d+)'", + r"[^']*)'\s+" + r"href='(?Phttps://(?:www\.)?siska\.(?:tv|video)/video\.php\?videoID=\d+)'", re.IGNORECASE, ) _DUR_RE = re.compile(r"th_video_duration'>\s*([\d]{1,2}(?:\s*:\s*[\d]{2}){1,2})\s*<") @@ -49,6 +56,10 @@ _ALT_RE = re.compile(r"alt='([^']*)'") _CAT_RE = re.compile(r"/category/([^/]+)/", re.IGNORECASE) +def _canonical(url: str) -> str: + return _HOST_RE.sub(_BASE + "/", url) + + def _parse_duration(text: str | None) -> int | None: """`40 : 27` → 2427 (MM:SS); `1 : 05 : 30` → H:MM:SS. None gdy brak.""" if not text: @@ -73,11 +84,13 @@ class SiskaScraper(BaseBrowseScraper): sitetag = "siskavideo" def _listing_url(self, page: int) -> str: - return f"{_BASE}/page/{page}/" + # Na siska.tv `/page//` oddaje zawsze strone 1. Najnowsze po kolei (18 na + # strone, videoID malejaco) sa pod `best_xvideos.php`, mimo nazwy. + return f"{_BASE}/best_xvideos.php?page={page}" # crawl_page nadpisany → poniższe abstrakcje nieużywane, ale wymagane do instancji. def _extract_scene_urls(self, listing_html: str) -> list[str]: - return [m.group("url") for m in _A_RE.finditer(listing_html)] + return [_canonical(m.group("url")) for m in _A_RE.finditer(listing_html)] def _parse_detail(self, scene_url: str, detail_html: str) -> RawScene | None: return None @@ -94,7 +107,7 @@ class SiskaScraper(BaseBrowseScraper): out: list[RawScene] = [] seen: set[str] = set() for m in _A_RE.finditer(html): - scene_url = m.group("url") + scene_url = _canonical(m.group("url")) if scene_url in seen: continue seen.add(scene_url) @@ -106,7 +119,7 @@ class SiskaScraper(BaseBrowseScraper): dm = _DUR_RE.search(window) duration_sec = _parse_duration(dm.group(1) if dm else None) tm = _THUMB_RE.search(window) - thumbnail_url = tm.group(1) if tm else None + thumbnail_url = _canonical(tm.group(1)) if tm else None # alt='Performer - Tytuł - Studio' → performer (pierwszy), studio (ostatni). performers: list[RawPerformer] = []