diff --git a/app/connectors/direct_scrapers/watchporn.py b/app/connectors/direct_scrapers/watchporn.py
index 839a566..4ba5526 100644
--- a/app/connectors/direct_scrapers/watchporn.py
+++ b/app/connectors/direct_scrapers/watchporn.py
@@ -32,6 +32,21 @@ _SCENE_URL_RE = re.compile(r'href="(https://watchporn\.to/video/\d+/[a-z0-9\-]+/
_MODEL_RE = re.compile(r'href="https://watchporn\.to/models/([a-z0-9\-]+)/"[^>]*>([^<]+)', re.IGNORECASE)
_TAG_RE = re.compile(r'href="https://watchporn\.to/tags/([a-z0-9\-]+)/"[^>]*>([^<]+)', re.IGNORECASE)
_CAT_RE = re.compile(r'href="https://watchporn\.to/categories/([a-z0-9\-]+)/"[^>]*>([^<]+)', re.IGNORECASE)
+# Nowy motyw od ok. 2026-10-10 (prefiks klas `wp-`). Obsada sceny siedzi TYLKO w bloku
+# `
`, nazwa w `
` (nie w tekscie linku),
+# a karty "related" ponizej maja wlasne linki `/models/` z nazwa w tekscie. Stary globalny
+# regex gubil prawdziwa obsade (pusty tekst linku) i dokladal performerow z kart related:
+# srednio 12,5 na scene zamiast 2 (10 do 13.10, 193 sceny). Kategorie to teraz gatunki
+# (chipy `wp-vchip`), studio jest w linku `/sites//` i w JSON-LD "publisher".
+_NEW_THEME_MARK = 'class="wp-vi__panel"'
+_CASTROW_RE = re.compile(r'(.*?)
', re.IGNORECASE | re.DOTALL)
+_CAST_RE = re.compile(
+ r'href="https://watchporn\.to/models/([a-z0-9\-]+)/"[^>]*>.*?([^<]+)',
+ re.IGNORECASE | re.DOTALL,
+)
+_CHIPS_RE = re.compile(r'(.*?)
', re.IGNORECASE | re.DOTALL)
+_SITE_RE = re.compile(r'href="https://watchporn\.to/sites/([a-z0-9\-]+)/"', re.IGNORECASE)
+_PUBLISHER_RE = re.compile(r'"publisher"\s*:\s*\{[^}]*?"name"\s*:\s*"([^"]+)"', re.DOTALL)
_DUR_RE = re.compile(r'"duration"\s*:\s*"([^"]+)"')
_UPLOAD_RE = re.compile(r'"uploadDate"\s*:\s*"([^"]+)"')
_VIDEO_ID_RE = re.compile(r"/video/(\d+)/")
@@ -74,9 +89,18 @@ class WatchPornScraper(BaseBrowseScraper):
um = _UPLOAD_RE.search(detail_html)
release_date = _parse_iso_date(um.group(1)) if um else None
+ new_theme = _NEW_THEME_MARK in detail_html
+
performers: list[RawPerformer] = []
seen_p: set[str] = set()
- for m in _MODEL_RE.finditer(detail_html):
+ if new_theme:
+ # Tylko blok obsady; brak bloku = scena bez obsady, NIE wracamy do globalnego
+ # regexu, bo ten zbiera karty related.
+ crm = _CASTROW_RE.search(detail_html)
+ cast_iter = _CAST_RE.finditer(crm.group(1)) if crm else iter(())
+ else:
+ cast_iter = _MODEL_RE.finditer(detail_html)
+ for m in cast_iter:
slug = m.group(1)
name = html.unescape(m.group(2)).strip()
if slug.isdigit() or slug in seen_p or not name:
@@ -86,26 +110,45 @@ class WatchPornScraper(BaseBrowseScraper):
tags: list[RawTag] = []
seen_t: set[str] = set()
- for m in _TAG_RE.finditer(detail_html):
- slug = m.group(1)
- name = html.unescape(m.group(2)).strip()
+
+ def _add_tag(slug: str, raw_name: str) -> None:
+ name = html.unescape(raw_name).strip().lstrip("#").strip()
if slug in seen_t or not name:
- continue
+ return
seen_t.add(slug)
tags.append(RawTag(external_id=f"{self.sitetag}:tag:{slug}", name=name, slug=slug))
+ for m in _TAG_RE.finditer(detail_html):
+ _add_tag(m.group(1), m.group(2))
+ if new_theme:
+ # Kategorie-chipy sceny (Blowjob, MILF...) to gatunki, ida do tagow.
+ chm = _CHIPS_RE.search(detail_html)
+ if chm:
+ for m in _CAT_RE.finditer(chm.group(1)):
+ _add_tag(m.group(1), m.group(2))
+
thumbnail_url = (
meta_content(detail_html, property="og:image") or _derive_thumb(scene_url)
)
studio: RawStudio | None = None
- cm = _CAT_RE.search(detail_html)
- if cm:
- cname = html.unescape(cm.group(2)).strip()
- if cname:
- studio = RawStudio(
- external_id=f"{self.sitetag}:studio:{slugify(cname)}", name=cname, slug=slugify(cname)
- )
+ if new_theme:
+ sm = _SITE_RE.search(detail_html)
+ if sm:
+ pm = _PUBLISHER_RE.search(detail_html)
+ cname = html.unescape(pm.group(1)).strip() if pm else sm.group(1).replace("-", " ").title()
+ if cname:
+ studio = RawStudio(
+ external_id=f"{self.sitetag}:studio:{slugify(cname)}", name=cname, slug=slugify(cname)
+ )
+ else:
+ cm = _CAT_RE.search(detail_html)
+ if cm:
+ cname = html.unescape(cm.group(2)).strip()
+ if cname:
+ studio = RawStudio(
+ external_id=f"{self.sitetag}:studio:{slugify(cname)}", name=cname, slug=slugify(cname)
+ )
return RawScene(
external_id=f"{self.sitetag}:{scene_url}",