goon/app/extractors/tubes/porntrex.py
https://github.com/goon-foss/goon 28273eda02 Dedykowane resolvery: xtremestream + porntrex KVS
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>
2026-05-22 11:54:47 +02:00

82 lines
2.9 KiB
Python

"""porntrex.com — KVS engine direct stream extractor.
2026-05-22: VPS Hetzner IP znów dociera do porntrex (HTTP 200) — wcześniej blokada
trzymała `porntrexcom` na `_vps_blocked_fallback`. Patrz [[goon_porntrex_vps_unblocked]].
KVS player: detail page ma `flashvars` z `video_url` / `video_alt_url` / `video_alt_url2`
(480p / 720p / 1080p), każdy to `get_file/<srv>/<token>/<path>.mp4/` URL.
`get_file` 302 → `cdn.pcdn.cloudswitches.com/...mp4?expires=<ts>&md5=<sig>` — to
**time-bound signed URL** (nie IP-bound). Mobile ExoPlayer może pobrać get_file
(follow 302) i grać direct z CDN — zero VPS bandwidth. Stąd `mobile_direct_ok=True`.
Token w `get_file` URL bywa single-use (410 po reuse), więc NIE resolvujemy 302 na
backendzie — oddajemy get_file URL, mobile zużywa token sam przy pierwszym fetchu.
"""
from __future__ import annotations
import logging
import re
from app.extractors._fetch import fetch_tube_html
from app.extractors._models import StreamSource
log = logging.getLogger(__name__)
_BASE = "https://www.porntrex.com"
# flashvars: `video_url: 'https://.../get_file/...mp4/'` + `video_url_text: '480p'`.
# Warianty: video_url, video_alt_url, video_alt_url2, video_alt_url3...
_URL_RE = re.compile(
r"(video(?:_alt)?_url\d*)\s*:\s*'(https?://[^']+/get_file/[^']+)'",
re.IGNORECASE,
)
_TEXT_RE = re.compile(
r"(video(?:_alt)?_url\d*)_text\s*:\s*'([^']*)'",
re.IGNORECASE,
)
def _quality_rank(label: str | None) -> int:
"""`1080p` → 1080, `720p HD` → 720. Do sortowania malejąco."""
if not label:
return -1
m = re.search(r"(\d{3,4})\s*p", label, re.IGNORECASE)
return int(m.group(1)) if m else -1
def extract(page_url: str, *, timeout: float = 60.0) -> list[StreamSource] | None:
html = fetch_tube_html(page_url, timeout=timeout)
# Mapa <var_name> → quality label (np. video_alt_url → "720p HD").
quality_by_var: dict[str, str] = {}
for m in _TEXT_RE.finditer(html):
quality_by_var[m.group(1).lower()] = m.group(2).strip()
seen: set[str] = set()
result: list[StreamSource] = []
for m in _URL_RE.finditer(html):
var_name = m.group(1).lower()
url = m.group(2)
if url in seen:
continue
seen.add(url)
quality = quality_by_var.get(var_name)
result.append(
StreamSource(
link=url,
type="mp4",
quality=quality or None,
referer=_BASE + "/",
# CDN po 302 jest time-bound (expires+md5), nie IP-bound —
# mobile gra direct z get_file, zero VPS proxy bandwidth.
raw={"mobile_direct_ok": True},
)
)
if not result:
log.info("porntrex: no KVS video_url in flashvars on %s", page_url)
return None
result.sort(key=lambda s: _quality_rank(s.quality), reverse=True)
return result