goon/app/extractors/tubes/hentaigasm.py
goon-foss a5295be10e feat(hentai): konektor hentaigasm.com - trzecie zrodlo wertykalu
TRZECI niezalezny host w wertykale. Te same odcinki co hentaimama i xanimeporn, ale
znowu inne pliki: tutaj hgasm<N>.com/<Tytul Ze Spacjami>.mp4, tam gdvid.info/N/... i
download.xanimeporn.com/videos/1080p/[1080p]<id>.mp4. Zadna z trzech stron nie
re-hostuje pozostalych.

Metadane w CALOSCI z RSS-a i to nie skrot tylko koniecznosc:
- strona odcinka NIE MA daty publikacji w zadnym standardowym miejscu (ani JSON-LD,
  ani article:published_time, ani <time>), a feed ma pubDate
- /genre/ w HTML to 38 linkow, czyli kolejna chmura z sidebara; feed daje 5 realnych
  kategorii, gdzie pierwsza to nazwa serii
- /page/2/ nie zwraca odcinkow w ogole, a /feed/?paged=N stronicuje chronologicznie

Playback najprostszy z calej trojki: konfiguracja JWPlayera z gotowym file: wprost w
HTML, bez AJAX-a i bez Cloudflare. Sciezka zawiera SPACJE, wiec kodujemy sama sciezke,
zostawiajac schemat i host. Host ROTUJE miedzy odcinkami (hgasm1/hgasm3), wiec czytamy
go ze strony zamiast wpisywac na sztywno.

Zweryfikowane z VPS: 206 na Range, ftypisom, 198 MB, dziala BEZ Referera i bez tokenu,
wiec URL nie wygasa. mobile_direct_ok.

Pilot: seen 107, new 107, errors 0. Metadane: 100 procent z data, miniaturka i tagami.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 14:57:28 +02:00

67 lines
2.4 KiB
Python

"""hentaigasm.com — bezpośredni mp4 z konfiguracji JWPlayera. Dodany 2026-08-04.
Najprostszy z całego wertykału hentai. Konfiguracja playera siedzi WPROST w HTML strony
odcinka, bez AJAX-a i bez Cloudflare:
jwplayer("player_01").setup({
file: "https://hgasm1.com/Nuki Nuki Zupposism 2 Subbed.mp4",
image: "https://hgasm1.com/thumbnail/Nuki Nuki Zupposism 2 Subbed.jpg",
...
})
Dla porównania: [[hentaimama]] trzyma to za POST-em na `admin-ajax` schowanym za
Cloudflare, a [[xanimeporn]] ma Fluid Player z pustym `<source>` i realny link dopiero
w zakładce „Downloads".
**Ścieżka zawiera SPACJE** i trzeba je zakodować — bez tego request się wywala. Kodujemy
tylko część po hoście, żeby nie ruszyć schematu ani kropek w domenie.
Zweryfikowane z VPS-a: 206 na Range, `ftypisom` w pierwszym KB (faststart), 198 MB,
działa BEZ Referera i bez tokenu, więc URL nie wygasa. Stąd `mobile_direct_ok`.
"""
from __future__ import annotations
import logging
import re
from urllib.parse import quote, urlsplit, urlunsplit
from app.extractors._fetch import fetch_tube_html
from app.extractors._models import StreamSource
log = logging.getLogger(__name__)
_BASE = "https://hentaigasm.com"
_JW_FILE_RE = re.compile(r"file:\s*[\"'](https?://[^\"']+\.mp4)[\"']")
def _encode(url: str) -> str:
"""Zakoduj spacje w ścieżce, zostawiając schemat i host nietknięte."""
parts = urlsplit(url)
return urlunsplit(
(parts.scheme, parts.netloc, quote(parts.path), parts.query, parts.fragment)
)
def extract(page_url: str, *, timeout: float = 60.0) -> list[StreamSource] | None:
html_text = fetch_tube_html(page_url, timeout=timeout)
seen: set[str] = set()
out: list[StreamSource] = []
for raw in _JW_FILE_RE.findall(html_text):
link = _encode(raw.strip())
if link in seen:
continue
seen.add(link)
out.append(
StreamSource(
link=link,
type="mp4",
quality=None, # serwis publikuje jedną rendycję, bez etykiety
referer=_BASE + "/",
# Zweryfikowane: 206 bez Referera, bez tokenu, faststart.
raw={"mobile_direct_ok": True},
)
)
if not out:
log.info("hentaigasm: brak file: w konfiguracji JWPlayera na %s", page_url)
return None
return out