Mixdrop (bug #3/#10 czarny ekran): wymagane UA+Accept headers (bez nich shell bez P.A.C.K.E.R.). Detect dead-video page -> raise HosterDead zamiast None (mobile dostaje skip-to-next sygnal). Dispatch regex obejmuje nowy canonical domain `miixdrop` (double-i). Yespornvip (bug #1): nowy KVS engine extractor. Origin `tube:yespornvip` istnial w playback_sources ale brak handlera w _REGISTRY -> try_extract None. Flashvars `video_url: 'function/0/<get_file_url>'`, function/0 to passthrough. 480p mp4 z mobile_direct_ok=True. Freshporno (bug #9 revert): wrocony na _vps_blocked_fallback (WebView path). Krotko-zywy switch na native extract z force_proxy=True cofniety bo app idzie publicznie - VPS bandwidth/anonimowosc priorytet nad UX flicker. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
100 lines
4 KiB
Python
100 lines
4 KiB
Python
"""Mixdrop embed hoster — P.A.C.K.E.R. eval → MDCore.wurl direct mp4.
|
|
|
|
Pattern (verified 2026-05-15 via curl_cffi impersonate=chrome120):
|
|
1. Fetch `https://mixdrop.my/e/<id>` → 200 z 95KB body, redirect 301 do
|
|
`https://m1xdrop.bz/e/<id>` (current TLD).
|
|
2. Body zawiera P.A.C.K.E.R. obfuscated JS block:
|
|
`eval(function(p,a,c,k,e,d){...}('...packed...',N,N,'...|...'.split('|'),0,{}))`
|
|
3. yt-dlp's `decode_packed_codes()` rozkrywa do ~390 chars JavaScript:
|
|
`MDCore.wurl="//a-delivery22.mxcontent.net/v2/<id>.mp4?s=<sig>&e=<exp>&_t=<ts>"`
|
|
4. URL na `mxcontent.net` zwraca **direct mp4** (Content-Type: video/mp4,
|
|
Content-Length: ~485MB) — działa z Hetzner VPS IP, brak token IP-bind.
|
|
|
|
`s` to signed token (HMAC?), `e` to expiry timestamp (unix sec), `_t` to
|
|
issued timestamp. Token jest valid ~24h od `_t`. Refetching embed page po
|
|
expiry zwraca nowy URL.
|
|
|
|
Active mango movies: 203 playbacks origin='mangoporn:mixdrop' w DB.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import re
|
|
|
|
from app.extractors._fetch import _DEFAULT_UA, browser_get
|
|
from app.extractors._models import HosterDead, StreamSource
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
_PACKER_RE = re.compile(
|
|
r"eval\(function\(p,a,c,k,e,d\)\{.+?\}\(.+?\)\)",
|
|
re.DOTALL,
|
|
)
|
|
_MP4_URL_RE = re.compile(r'MDCore\.wurl\s*=\s*"([^"]+\.mp4[^"]*)"')
|
|
# Dead-video page (200 OK but no packer, only the "sorry" shell). Wcześniej nasz
|
|
# extractor zwracał None bez sygnału "dead" → playback.py nie ustawiał dead_at,
|
|
# mobile dostawał pusty wynik → czarny ekran zamiast skip-to-next-source.
|
|
_DEAD_RE = re.compile(
|
|
r"can't find the video|WE ARE SORRY",
|
|
re.IGNORECASE,
|
|
)
|
|
|
|
|
|
def extract(page_url: str, *, timeout: float = 30.0) -> list[StreamSource] | None:
|
|
# UA + Accept są wymagane — bez nich mixdrop dla VALID video zwraca minimalny
|
|
# body bez P.A.C.K.E.R. (sam stream_proxy._refetch_mixdrop_url też tak robi).
|
|
# Brak headerów powodował że extract() na żywym mixdrop ID dostawał shell bez
|
|
# packera → no match → None → mobile dostawał czarny ekran.
|
|
headers = {
|
|
"User-Agent": _DEFAULT_UA,
|
|
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
|
"Accept-Language": "en-US,en;q=0.9",
|
|
}
|
|
res = browser_get(page_url, headers=headers, timeout=timeout)
|
|
if res.status_code != 200 or not res.text:
|
|
log.info("mixdrop: fetch fail status=%s url=%s", res.status_code, page_url)
|
|
return None
|
|
|
|
m = _PACKER_RE.search(res.text)
|
|
if not m:
|
|
if _DEAD_RE.search(res.text):
|
|
raise HosterDead(f"mixdrop {page_url}: video not found")
|
|
log.info("mixdrop: no P.A.C.K.E.R. block in %s (page changed?)", page_url)
|
|
return None
|
|
|
|
try:
|
|
from yt_dlp.utils import decode_packed_codes
|
|
decoded = decode_packed_codes(m.group(0))
|
|
except Exception as e:
|
|
log.warning("mixdrop: decode_packed_codes failed: %s", e)
|
|
return None
|
|
|
|
url_match = _MP4_URL_RE.search(decoded)
|
|
if not url_match:
|
|
log.info("mixdrop: no MDCore.wurl in decoded payload (len=%d)", len(decoded))
|
|
return None
|
|
|
|
raw_url = url_match.group(1)
|
|
# URL z mixdrop często jest protocol-relative (`//a-delivery22...`).
|
|
if raw_url.startswith("//"):
|
|
raw_url = "https:" + raw_url
|
|
|
|
return [
|
|
StreamSource(
|
|
link=raw_url,
|
|
quality=None, # mixdrop nie listuje quality variants w MDCore
|
|
type="mp4",
|
|
referer="https://mixdrop.my/",
|
|
# mxcontent CDN wymaga **same-session cookies** z embed page +
|
|
# Chrome JA3. Backend `extract` zamyka sesję po fetch → mobile
|
|
# próbuje mp4 bez cookies → 403. Proxy MUSI re-fetchować embed
|
|
# w fresh curl_cffi session, extract nowy mp4 URL, stream.
|
|
# `refetch_url` w raw → token field `rf` → proxy refresh logic.
|
|
raw={
|
|
"proxy_impersonate": True,
|
|
"refetch_url": page_url, # embed page do re-extract
|
|
"refetch_hoster": "mixdrop",
|
|
},
|
|
)
|
|
]
|