1745 wystapien w 291 plikach (backend, scripts, mobile). Dominowal jeden ksztalt, ' — ' laczace zdania, wiec poszedl przecinek; myslnik otwierajacy mysl zdjety; zakresy 0–5 i A–Z na dywiz. Podzial komentarz/kod robil skaner stanow, nie regex, zeby nie ruszyc myslnika stojacego w stringu albo w wyrazeniu regularnym. Potwierdzone maszynowo: po wycieciu komentarzy z wersji HEAD i z dysku obie sa identyczne we wszystkich 291 plikach, czyli poza komentarzami nie zmienilo sie nic. 233 pliki .py parsuja sie bez bledu, tsc ma te same 11 bledow w tych samych 3 plikach co przed zmiana. Dwie rzeczy wymagaly poprawki po automacie: wyrownane listy definicji, gdzie myslnik byl separatorem kolumny (37 miejsc, kolumna przywrocona), i trzy przeniesienia mysli do nowej linii, gdzie zostawal wiszacy przecinek na poczatku (poprawione recznie). Bundla nie republikuje: Hermes i tak nie niesie komentarzy, wiec dla uzytkownika to zero zmiany. Co-Authored-By: Claude Opus 5 <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",
|
|
},
|
|
)
|
|
]
|