Animacje SFM/Blender. Kanon nie istnieje tak samo jak przy hentai, wiec kazda pozycja jest orphanem i to poprawne. Playback: KVS z jawnymi flashvars (4 jakosci, bez license_code), ale `v-acctoken` jest ZWIAZANY Z IP tego, kto pobral strone. Test krzyzowy: URL wygenerowany przez proxy i pobrany z VPS = 403, ten sam URL z IP generujacego = 206 ftypisom 70 MB. Po zdekodowaniu tokenu adresu nie widac wprost (inaczej niz fpoxxx), wiec samo ogladanie nie wystarcza. Dlatego bez mobile_direct_ok: telefon resolwuje sam, UPFRONT, istniejaca sciezka isGetFileUrl()/resolveGetFilePage z SceneDetailScreen. Rozszerzam getfileResolver o flashvars, bo dotad umial tylko <source> (hdporn.gg, fullmovies.xxx). Zweryfikowane end-to-end z residential IP: strona -> flashvars 720p -> 302 na boomio-cdn -> 206 ftypisom 39,8 MB. Bramka dlugosci zestrojona z `min_ingest_duration_sec` (prod 180 s) zamiast wlasnej liczby: pierwsza wersja miala 60 s i liczniki klamaly (seen 57, new 31, skipped 26). Po zestrojeniu seen 20, new 20, skipped 0. Krotka tresc wycina ~70% katalogu (mediana 68 s). Tagi TYLKO z class=tag_item. `class=button` wyglada na wlasciwe, a jest chmura "Top Tags": przy filmie o Lux siedzialy tam "cell (dbz)" i "queen of pain (dota)". Autor animacji zapisany jako studio.
69 lines
3 KiB
Python
69 lines
3 KiB
Python
"""rule34video.com — KVS `get_file` ZWIĄZANY Z IP. Dodany 2026-08-13.
|
|
|
|
Silnik to KVS, ale bez zaciemniania: flashvars trzymają cztery gotowe URL-e, bez
|
|
`function/0/` i bez potrzeby dekodowania `license_code`:
|
|
|
|
video_url: …/get_file/51/<hash>/4543000/4543893/4543893_360.mp4/?v-acctoken=…
|
|
video_alt_url: … _480p.mp4 (video_alt_url_text: '480p')
|
|
video_alt_url2: … _720p.mp4 (video_alt_url2_text: '720p')
|
|
video_alt_url3: … _1080p.mp4 (video_alt_url3_text: '1080p')
|
|
|
|
**`v-acctoken` jest związany z IP tego, kto pobrał STRONĘ.** Test krzyżowy 2026-08-13:
|
|
URL wygenerowany przez proxy i pobrany z VPS-a → **403**; ten sam URL pobrany z IP, które
|
|
go wygenerowało → 206, `ftypisom`, 70 MB. Po zdekodowaniu (`4533|163838|0|<md5>`) adresu
|
|
nie widać wprost, w odróżnieniu od fpoxxx, gdzie IP siedziało w treści tokenu — czyli
|
|
samo oglądanie tokenu NIE wystarcza, żeby to wykryć, trzeba zrobić test krzyżowy.
|
|
|
|
Dlatego **NIE ma tu `mobile_direct_ok`**: URL zresolwowany na VPS-ie jest dla telefonu
|
|
martwy. Zamiast tego oddajemy go jako zwykły link `get_file`, a resztę robi istniejąca
|
|
maszyneria po stronie telefonu: `SceneDetailScreen` wykrywa `isGetFileUrl()` i JESZCZE
|
|
PRZED wejściem do playera pobiera stronę z telefonu (token dla IP telefonu), a
|
|
`getfileResolver` wyciąga z niej flashvars. Ten sam mechanizm obsługuje hdporn.gg
|
|
i fullmovies.xxx, tam różnicą jest tylko `<source>` zamiast flashvars.
|
|
|
|
Backend zwraca więc URL-e po to, żeby apka znała listę jakości i etykiety, nie po to,
|
|
żeby je odtwarzać z VPS-a.
|
|
"""
|
|
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://rule34video.com"
|
|
_URL_RE = re.compile(r"(video_(?:alt_)?url\d*)\s*:\s*'(https?://[^']+\.mp4[^']*)'")
|
|
_LABEL_RE = re.compile(r"(video_(?:alt_)?url\d*)_text\s*:\s*'([^']{1,12})'")
|
|
#: Kolejność od najlepszej jakości. 4K nie występuje, serwis kończy na 1080p.
|
|
_QUALITY_RANK = {"1080p": 0, "720p": 1, "480p": 2, "360p": 3}
|
|
|
|
|
|
def extract(page_url: str, *, timeout: float = 60.0) -> list[StreamSource] | None:
|
|
html_text = fetch_tube_html(page_url, timeout=timeout)
|
|
labels = dict(_LABEL_RE.findall(html_text))
|
|
|
|
out: list[StreamSource] = []
|
|
seen: set[str] = set()
|
|
for var, link in _URL_RE.findall(html_text):
|
|
if link in seen:
|
|
continue
|
|
seen.add(link)
|
|
out.append(
|
|
StreamSource(
|
|
link=link,
|
|
type="mp4",
|
|
quality=labels.get(var),
|
|
referer=_BASE + "/",
|
|
# Bez `mobile_direct_ok`: token jest IP-bound, telefon resolwuje sam.
|
|
raw={},
|
|
)
|
|
)
|
|
if not out:
|
|
log.info("rule34video: brak video_url we flashvars na %s", page_url)
|
|
return None
|
|
|
|
out.sort(key=lambda s: _QUALITY_RANK.get((s.quality or "").lower(), 99))
|
|
return out
|