goon/app/extractors/hosters/mixdrop.py
goon-foss ad0284585b Initial commit
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.
2026-05-20 10:10:22 +02:00

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",
},
)
]