Goon — self-hosted aggregator for adult-content scene metadata. Indexes scenes from TPDB, StashDB, and 30+ public adult tube sites. Cross-source deduplication via perceptual hash + Levenshtein distance. FastAPI backend + APScheduler worker + React Native (Expo) mobile client. FOSS, ad-free, donation-funded. See README for details.
109 lines
3.7 KiB
Python
109 lines
3.7 KiB
Python
"""Wybór najlepszego iframe-hostera z page HTML tube'a typu wrapper.
|
|
|
|
Tube'y typu siska/perverzija/latestpornvideo nie hostują player'a same — embedują
|
|
zewnętrznych hosterów (luluvid, doodporn, mixdrop, streamtape, ...). Detail page
|
|
zawiera typowo 2-5 iframes:
|
|
- ad-iframes (willingcease.com, popads, discord, about:blank)
|
|
- dead hosters (streamtape malware, openload offline)
|
|
- file-hosters (rapidgator, nitroflare — premium walled)
|
|
- "fake" hosters typu playmogo (niszowe forki, brak extractora)
|
|
- real KVS hosters (luluvid, doodporn, mixdrop, voe — `extract_stream_from_hoster`
|
|
radzi sobie z nimi przez KVS markers + yt-dlp generic)
|
|
|
|
`extract_best_iframe()` filtruje śmieci i preferuje hostery known-working z naszej
|
|
implementacji. Używane przez scrapery (siska, latestpornvideo, perverzija) ŚW
|
|
2-etapowym scrape: page listing → URL sceny → fetch detail → pick iframe → save
|
|
jako `embed_url` w RawPlaybackSource.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
|
|
from app.extractors.tubes._embed_iframe import DEAD_HOSTER_RE
|
|
|
|
# Ad / popup iframes — popunder networks + Discord widget + about:blank.
|
|
# Niektóre tube'y embedują kilka takich PRZED prawdziwym player'em żeby
|
|
# zmonetyzować ruch — naszego extractora trzeba przeskoczyć żeby trafić na video.
|
|
_AD_IFRAME_RE = re.compile(
|
|
r"willingcease\.com"
|
|
r"|popads|popunder|adsterra"
|
|
r"|discord\.com/widget"
|
|
r"|about:blank",
|
|
re.IGNORECASE,
|
|
)
|
|
|
|
# Hostery których NIGDY nie obsłużymy direct extract (premium / file storage /
|
|
# proprietary players bez KVS / playmogo-style niche). Mobile WebView może je
|
|
# otworzyć, ale priorytet w pick'u to im trafić jako fallback NA OSTATNIM
|
|
# miejscu, nie pierwszym.
|
|
_LOW_PRIORITY_HOSTERS_RE = re.compile(
|
|
r"rapidgator|nitroflare|filer\.net|frdl\."
|
|
r"|playmogo\.com" # generic player redirect, brak ext
|
|
r"|easyvidplayer|embedseek|upns\.online|seekplayer|rpmplay",
|
|
re.IGNORECASE,
|
|
)
|
|
|
|
# Hostery które MAMY potwierdzone że działają z `extract_stream_from_hoster`.
|
|
# Kolejność = priority list (lewy = najbardziej preferowany).
|
|
_PREFERRED_HOSTERS = (
|
|
"luluvid",
|
|
"lulustream",
|
|
"doodporn",
|
|
"doodstream",
|
|
"dood.la",
|
|
"mixdrop",
|
|
"voe.sx", # czasem działa
|
|
)
|
|
|
|
|
|
def _iframe_priority(url: str) -> int:
|
|
"""Lower = better. 0..N dla preferred, 500 dla nieznanych, 1000 low-priority."""
|
|
if _LOW_PRIORITY_HOSTERS_RE.search(url):
|
|
return 1000
|
|
for i, h in enumerate(_PREFERRED_HOSTERS):
|
|
if h in url.lower():
|
|
return i
|
|
return 500
|
|
|
|
|
|
_IFRAME_SRC_RE = re.compile(r'<iframe[^>]+src=["\']([^"\']+)["\']', re.IGNORECASE)
|
|
|
|
|
|
def extract_best_iframe(html: str, *, base_url: str | None = None) -> str | None:
|
|
"""Zwraca URL pierwszego iframe NIE-ad, NIE-dead, posortowanego po priority.
|
|
|
|
`base_url` używany do resolwowania protocol-relative (//host/path) i relative
|
|
URL-i (/embed/xxx) na absolute. Jeśli None i iframe jest relative, jest
|
|
pomijany.
|
|
"""
|
|
if not html:
|
|
return None
|
|
|
|
urls: list[str] = []
|
|
for m in _IFRAME_SRC_RE.finditer(html):
|
|
src = m.group(1).strip()
|
|
if not src or src.lower() == "about:blank":
|
|
continue
|
|
if src.startswith("//"):
|
|
src = "https:" + src
|
|
elif src.startswith("/") and base_url:
|
|
from urllib.parse import urljoin
|
|
src = urljoin(base_url, src)
|
|
urls.append(src)
|
|
|
|
if not urls:
|
|
return None
|
|
|
|
clean = []
|
|
for u in urls:
|
|
if _AD_IFRAME_RE.search(u):
|
|
continue
|
|
if DEAD_HOSTER_RE.search(u):
|
|
continue
|
|
clean.append(u)
|
|
|
|
if not clean:
|
|
return None
|
|
|
|
clean.sort(key=_iframe_priority)
|
|
return clean[0]
|