xtremestream (perverzija): - extract_stream_from_hoster special-case: embed /player/index.php?data=<H> → m3u8 master = /player/xs1.php?data=<H> (z inline JS m3u8_loader_url) - Wcześniej brak packera/file w videojs HTML → WebView fallback porntrex (KVS) — VPS znów ma dostęp 2026-05-22: - Nowy app/extractors/tubes/porntrex.py — flashvars video_url/_alt_url → get_file URLs (480/720/1080p) - get_file 302 → CDN time-bound signed (expires+md5, NIE IP-bound) → mobile_direct_ok=True, mobile gra direct, zero VPS bandwidth - _REGISTRY: porntrexcom _vps_blocked_fallback → porntrex.extract bysezoxexe (latestpornvideo 2nd embed) — filemoon-rebrand Vite SPA, wymaga osobnego RE; latestpornvideo i tak działa przez luluvid. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
162 lines
7.3 KiB
Python
162 lines
7.3 KiB
Python
"""Stream URL extractors per-tube.
|
|
|
|
Public API:
|
|
- `try_extract(sitetag, page_url) -> list[StreamSource] | None`
|
|
- `StreamSource` (dataclass)
|
|
- `HosterDead` (exception)
|
|
- `extract_stream_from_hoster(iframe_url, *, referer)` — generic packer-based hoster extract
|
|
- `fetch_tube_html(url)` — Chrome TLS fingerprint fetch (curl_cffi)
|
|
- `browser_get(url)` — low-level
|
|
|
|
Architektura: każdy tube ma osobny moduł `app.extractors.tubes.<tube>` który eksportuje
|
|
`extract(page_url) -> list[StreamSource] | None`. Registry niżej mapuje sitetag →
|
|
modułowy extractor. `try_extract()` to thin wrapper z exception handlingiem.
|
|
|
|
Po removalu porn-app dependency, ten moduł jest jedynym mechanizmem rozwiązywania
|
|
streamów — playback.py nie wpada już do porn-app /stream API.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from collections.abc import Callable
|
|
|
|
from app.extractors._fetch import browser_get, fetch_tube_html
|
|
from app.extractors._models import HosterDead, StreamSource, TubePageError
|
|
from app.extractors.hoster import extract_stream_from_hoster, unpack_packer
|
|
from app.extractors.tubes import (
|
|
_embed_iframe,
|
|
_vps_blocked_fallback,
|
|
_ytdlp,
|
|
eporner,
|
|
freshporno,
|
|
hqporner,
|
|
latestpornvideo,
|
|
paradisehill,
|
|
porn00,
|
|
pornhat,
|
|
porntrex,
|
|
pornxp,
|
|
sxyprn,
|
|
)
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
# Sitetag → extractor function. Sitetag pasuje do format'u z origin: `pornapp:<sitetag>`
|
|
# (lub po Fazie 2 migracji: `tube:<sitetag>`).
|
|
#
|
|
# Mainstream tubes (pornhub/xvideos/xnxx/xhamster/redtube/youporn/porntrex) używają
|
|
# yt-dlp jako extractor — battle-tested, aktualizowane przez upstream przy zmianach
|
|
# HTML. Aggregator tubes (xmoviesforyou/watchporn/siska/...) używają generic
|
|
# embed-iframe extractor (page → /e/<id> iframe → P.A.C.K.E.R. unpack). Custom kod
|
|
# tylko tam gdzie tube ma niestandardowy schemat (eporner XHR, sxyprn URL transform).
|
|
_REGISTRY: dict[str, Callable[[str], list[StreamSource] | None]] = {
|
|
# hqporner — CDN URLs IP-bound do VPS, force_proxy wymusza ruch przez VPS proxy.
|
|
# 2026-05-20 (pre-public): bandwidth + anonimowość VPS > UX. Switch na WebView
|
|
# fallback — mobile pobiera embed iframe z phone IP, FluidPlayer JS decoduje
|
|
# mp4, ExoPlayer odtwarza direct z phone CDN session. **0 VPS bandwidth + VPS
|
|
# IP nie ujawniony** (mobile nie łączy się z VPS proxy URL).
|
|
# Trade-off: WebView ma 1 extra step (page → player JS) ale bez popup-ads jak
|
|
# hqporner.com bo INJECTED_JS w PlayerScreen.tsx blokuje + scrape `<source>.src`.
|
|
"hqpornercom": _vps_blocked_fallback.extract,
|
|
"epornercom": eporner.extract,
|
|
"sxyprncom": sxyprn.extract,
|
|
# Mainstream tubes — yt-dlp
|
|
# NB: 2026-05-18 cross-IP test potwierdził że xvideos/xnxx/pornhub/youporn/redtube
|
|
# CDN URLs są **time-bound** (nie IP-bound) — mobile_direct_ok auto-detect w
|
|
# playback.py daje mobile direct fetch, zero VPS bandwidth.
|
|
"pornhubcom": _ytdlp.extract,
|
|
"redtubecom": _ytdlp.extract,
|
|
"xvideoscom": _ytdlp.extract,
|
|
"xnxxcom": _ytdlp.extract,
|
|
"youporncom": _ytdlp.extract,
|
|
# porntrex KVS — 2026-05-22 VPS znów dociera (HTTP 200). Dedykowany extractor:
|
|
# flashvars `video_url` → `get_file` 302 → CDN time-bound signed URL
|
|
# (`expires`+`md5`, NIE IP-bound) → mobile gra direct, zero VPS bandwidth.
|
|
"porntrexcom": porntrex.extract,
|
|
# VPS-blocked tubes — KVS / Cloudflare blokuje Hetzner IP, ale działają z residential
|
|
# IP (potwierdzone Chrome DevTools MCP 2026-05-15). Mobile WebView + INJECTED_JS
|
|
# (PlayerScreen.tsx:805) skanuje <video>.src + XHR — łapie URL po decode-ie player JS.
|
|
"xhamstercom": _vps_blocked_fallback.extract,
|
|
"porndittcom": _vps_blocked_fallback.extract,
|
|
"fpoxxx": _vps_blocked_fallback.extract,
|
|
"sxylandcom": _vps_blocked_fallback.extract,
|
|
# Aggregator tubes — generic embed-iframe → hoster unpacker
|
|
"latestpornvideocom": latestpornvideo.extract,
|
|
"xmoviesforyoucom": _embed_iframe.extract,
|
|
"watchporn": _embed_iframe.extract,
|
|
"siskavideo": _embed_iframe.extract,
|
|
"porn4dayspw": _embed_iframe.extract,
|
|
"porndishcom": _embed_iframe.extract,
|
|
# xxxfreewatch — DELISTED 2026-05-18. 790 solo-orphan scen, 0% match, CF-walled z VPS.
|
|
"latestleaksco": _embed_iframe.extract,
|
|
"mypornerleakcom": _embed_iframe.extract,
|
|
# PornHat — dedicated extractor: tylko `<source>` z player area (skip sidebar
|
|
# trailer URLs `_preview*.mp4`), dedupe po filename. Get_file 302 → CDN, proxy
|
|
# follow_redirects=True wymagane (fix w stream_proxy.py).
|
|
"pornhatcom": pornhat.extract,
|
|
# Freshporno KVS — `cv=` HMAC signed token IP-bound do VPS. 2026-05-20 pre-public:
|
|
# bandwidth + VPS anonimowość priorytet. WebView fallback → mobile pobiera embed
|
|
# z phone IP, KVS player JS decoduje video_url, ExoPlayer odtwarza direct z CDN.
|
|
"freshpornoorg": _vps_blocked_fallback.extract,
|
|
# porn00 / pornxp — IP-bound CDN tokens. Pre-public WebView fallback (bandwidth +
|
|
# anonimowość VPS). Niski volume (84 scen), trivial.
|
|
"porn00org": _vps_blocked_fallback.extract,
|
|
"pornxpph": _vps_blocked_fallback.extract,
|
|
# Direct-scraping tubes (mają też search scraper w connectors/direct_scrapers/)
|
|
# — używają identycznego embed-iframe pattern dla streamingu.
|
|
# hdporn92com — DELISTED 2026-05-18. Scene pages to SEO shell bez player iframe,
|
|
# JS hijackuje kliki na popunder. Wszystkie playback_sources mass-marked dead.
|
|
# 0dayxx wraps watchporn.to embed. watchporn.to/get_file/ token IP-bound (302→410
|
|
# cross-IP). Switch na WebView fallback. ~5k scen.
|
|
"0dayxxcom": _vps_blocked_fallback.extract,
|
|
# CF-protected tube — curl_cffi w fetch_tube_html bypassa JA3, embed-iframe pattern.
|
|
"perverzijacom": _embed_iframe.extract,
|
|
# Special: WebView-only (Yii2 session-bound player).
|
|
"paradisehillcc": paradisehill.extract,
|
|
# PornDoe — dołączony 2026-05-21 (theporndude audit). Stream URL nie inline w
|
|
# SSR HTML (player JS init po Play click), więc WebView fallback: mobile pobiera
|
|
# /watch/<id> z phone IP, player JS dekoduje video.src, INJECTED_JS scrape.
|
|
# 0 VPS bandwidth — zgodne z pre-public bandwidth/anonimowość priorytet.
|
|
"porndoecom": _vps_blocked_fallback.extract,
|
|
}
|
|
|
|
|
|
def try_extract(sitetag: str, page_url: str) -> list[StreamSource] | None:
|
|
"""Próbuje rozwiązać stream URL dla danego tube'a + page_url.
|
|
|
|
Zwraca listę StreamSource (różne quality/kontener) lub None gdy:
|
|
- brak extractora dla tego sitetag
|
|
- extractor zwrócił None / nie znalazł URL'a
|
|
|
|
Raises HosterDead gdy embed page wprost mówi że video deleted/not found —
|
|
caller (playback.py) łapie i oznacza playback_source.dead_at.
|
|
"""
|
|
extractor = _REGISTRY.get(sitetag)
|
|
if extractor is None:
|
|
return None
|
|
try:
|
|
return extractor(page_url)
|
|
except (HosterDead, TubePageError):
|
|
raise
|
|
except Exception as e:
|
|
log.warning("extractor for %s failed on %s: %s", sitetag, page_url, e)
|
|
return None
|
|
|
|
|
|
def supported_sitetags() -> tuple[str, ...]:
|
|
"""Zwraca listę sitetag-ów które mają zarejestrowany extractor."""
|
|
return tuple(_REGISTRY.keys())
|
|
|
|
|
|
__all__ = [
|
|
"try_extract",
|
|
"supported_sitetags",
|
|
"StreamSource",
|
|
"HosterDead",
|
|
"TubePageError",
|
|
"extract_stream_from_hoster",
|
|
"unpack_packer",
|
|
"fetch_tube_html",
|
|
"browser_get",
|
|
]
|