Goon — self-hosted aggregator for adult-content scene metadata. Indexes scenes from TPDB, StashDB, and 30+ public adult tube sites. Cross-source deduplication via perceptual hash + Levenshtein distance. FastAPI backend + APScheduler worker + React Native (Expo) mobile client. FOSS, ad-free, donation-funded. See README for details.
82 lines
3.1 KiB
Python
82 lines
3.1 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 browser_get
|
|
from app.extractors._models import 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[^"]*)"')
|
|
|
|
|
|
def extract(page_url: str, *, timeout: float = 30.0) -> list[StreamSource] | None:
|
|
res = browser_get(page_url, 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:
|
|
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",
|
|
},
|
|
)
|
|
]
|