1745 wystapien w 291 plikach (backend, scripts, mobile). Dominowal jeden ksztalt, ' — ' laczace zdania, wiec poszedl przecinek; myslnik otwierajacy mysl zdjety; zakresy 0–5 i A–Z na dywiz. Podzial komentarz/kod robil skaner stanow, nie regex, zeby nie ruszyc myslnika stojacego w stringu albo w wyrazeniu regularnym. Potwierdzone maszynowo: po wycieciu komentarzy z wersji HEAD i z dysku obie sa identyczne we wszystkich 291 plikach, czyli poza komentarzami nie zmienilo sie nic. 233 pliki .py parsuja sie bez bledu, tsc ma te same 11 bledow w tych samych 3 plikach co przed zmiana. Dwie rzeczy wymagaly poprawki po automacie: wyrownane listy definicji, gdzie myslnik byl separatorem kolumny (37 miejsc, kolumna przywrocona), i trzy przeniesienia mysli do nowej linii, gdzie zostawal wiszacy przecinek na poczatku (poprawione recznie). Bundla nie republikuje: Hermes i tak nie niesie komentarzy, wiec dla uzytkownika to zero zmiany. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
81 lines
2.8 KiB
Python
81 lines
2.8 KiB
Python
"""Universal thumbnail URL extractor for tube pages.
|
|
|
|
Direct scrapery (search-only) zwracają RawScene z thumbnail_url=None dla większości
|
|
tube'ów (xnxx, hdporn92, sxyland, sxyprn). Detail page zawiera URL miniatury w
|
|
jednym z patternów:
|
|
|
|
1. **OpenGraph** (najbardziej powszechne):
|
|
`<meta property="og:image" content="https://cdn.../thumb.jpg">`
|
|
2. **Twitter Card** (fallback gdy og:image brak):
|
|
`<meta name="twitter:image" content="...">`
|
|
3. **Schema.org VideoObject LD-JSON**:
|
|
`"thumbnailUrl": "https://..."` lub `"thumbnailUrl": ["url1", "url2"]`
|
|
4. **html5player** (KVS-based, xnxx/xvideos):
|
|
`html5player.setThumbUrl('https://thumb-cdn77.xnxx-cdn.com/.../t.jpg')`
|
|
|
|
Funkcja zwraca pierwszy znaleziony URL (string), lub None gdy żaden nie pasuje.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
|
|
_OG_IMAGE_RE = re.compile(
|
|
r'<meta\s+property="og:image"\s+content="([^"]+)"', re.IGNORECASE
|
|
)
|
|
_TWITTER_IMAGE_RE = re.compile(
|
|
r'<meta\s+name="twitter:image(?::src)?"\s+content="([^"]+)"', re.IGNORECASE
|
|
)
|
|
_LD_THUMB_RE = re.compile(
|
|
r'"thumbnailUrl"\s*:\s*(?:"([^"]+)"|\[\s*"([^"]+)")', re.IGNORECASE
|
|
)
|
|
_KVS_THUMB_RE = re.compile(
|
|
r"setThumbUrl\(\s*['\"]([^'\"]+)['\"]", re.IGNORECASE
|
|
)
|
|
# hqporner: stare WordPress bez og:image / schema. Poster `_main.jpg` siedzi w
|
|
# atrybucie onclick (`changeImage('//cdn/_main.jpg', 'cover_<id>')`) lub w preload
|
|
# array (`preload_<id> = ['//.../_1.jpg', ...]`). Wyciągamy `_main.jpg` z CDN-a
|
|
# fastporndelivery.hqporner.com, to canonical poster, frames `_1..N.jpg` to
|
|
# hover animation.
|
|
_HQPORNER_THUMB_RE = re.compile(
|
|
r"['\"](//[a-z0-9.\-]*hqporner[a-z0-9.\-]*/imgs/[^'\"]+_main\.jpg)['\"]",
|
|
re.IGNORECASE,
|
|
)
|
|
|
|
|
|
def extract_thumbnail_url(html: str) -> str | None:
|
|
"""Zwraca pierwszą znalezioną miniaturkę URL lub None.
|
|
|
|
Kolejność: OG → Twitter → LD-JSON → KVS html5player. og:image jest
|
|
najpopularniejszy (większość WordPress + KVS-based tubes); pozostałe to
|
|
fallback dla niszowych templatek.
|
|
"""
|
|
if not html:
|
|
return None
|
|
|
|
if (m := _OG_IMAGE_RE.search(html)):
|
|
url = m.group(1).strip()
|
|
if url and not url.startswith("data:"):
|
|
return url
|
|
|
|
if (m := _TWITTER_IMAGE_RE.search(html)):
|
|
url = m.group(1).strip()
|
|
if url and not url.startswith("data:"):
|
|
return url
|
|
|
|
if (m := _LD_THUMB_RE.search(html)):
|
|
url = (m.group(1) or m.group(2) or "").strip()
|
|
if url:
|
|
return url
|
|
|
|
if (m := _KVS_THUMB_RE.search(html)):
|
|
url = m.group(1).strip()
|
|
if url:
|
|
return url
|
|
|
|
if (m := _HQPORNER_THUMB_RE.search(html)):
|
|
url = m.group(1).strip()
|
|
if url:
|
|
# Protocol-relative `//cdn/path` → https.
|
|
return "https:" + url if url.startswith("//") else url
|
|
|
|
return None
|