- Scraper DataLife Engine : recherche AJAX, fiches par saison, épisodes via ep-data.php (VF/VOSTFR), films via film_api.php, nouveautés /series/ - Config YAML externalisée (sélecteurs réparables sans coder) - Domaines étendus : uqload.vc, vidzy.cc/.live - 11 tests (126 au total)
79 lines
2.8 KiB
Python
79 lines
2.8 KiB
Python
"""Extracteur Vidzy (vidzy.org/.cc/.live) — page embed videojs → m3u8.
|
|
|
|
La page embed ne contient pas l'URL vidéo en clair : le script videojs appelle
|
|
une fonction de décodage inline `atob(s)` + reverse + XOR, avec une graine
|
|
dérivée du hostname (`somme des codes des caractères & 0xFF`). Si le décodage
|
|
échoue, la page sert un leurre `https://s1.fsvid.lol/troll/master.m3u8`
|
|
(même valeur pour tous les épisodes) — on le rejette explicitement.
|
|
|
|
Algorithme (reproduit fidèlement depuis le JS de la page) :
|
|
b = base64decode(s) ; a = b[::-1]
|
|
r[i] = chr(a[i] ^ ((0x3D + i*89 + H) & 0xFF)) avec H = sum(ord(hostname)) & 0xFF
|
|
"""
|
|
|
|
import base64
|
|
import logging
|
|
import re
|
|
from urllib.parse import urlparse
|
|
|
|
from app.config import get_settings
|
|
from app.scrapers.base import HosterExtractor, ScrapeError, VideoLink, register_hoster
|
|
from app.scrapers.http import fetch
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
_B64_RE = re.compile(
|
|
r"atob\(s\)(?s:.){0,800}?\}\)\(\"(?P<b64>[A-Za-z0-9+/=]{50,})\"",
|
|
)
|
|
_CLEAR_PATTERNS = (
|
|
re.compile(r'sources\s*:\s*\[\s*\{\s*src\s*:\s*["\'](?P<url>https?://[^"\']+?\.m3u8[^"\']*)["\']'),
|
|
re.compile(r'_fsvHls\s*=\s*"(?P<url>https?://[^"]+?\.m3u8[^"]*)"'),
|
|
)
|
|
_TROLL = "/troll/"
|
|
|
|
|
|
def _decode(b64: str, hostname: str) -> str | None:
|
|
try:
|
|
data = base64.b64decode(b64)
|
|
except (ValueError, TypeError):
|
|
return None
|
|
seed = sum(ord(c) for c in hostname) & 0xFF
|
|
decoded = "".join(chr(b ^ ((0x3D + i * 89 + seed) & 0xFF)) for i, b in enumerate(data[::-1]))
|
|
return decoded if decoded.startswith(("http://", "https://")) else None
|
|
|
|
|
|
@register_hoster
|
|
class VidzyExtractor(HosterExtractor):
|
|
name = "vidzy"
|
|
domains = ("vidzy.org", "vidzy.cc", "vidzy.live")
|
|
|
|
async def extract(self, embed_url: str) -> VideoLink:
|
|
html = await fetch(embed_url)
|
|
hostname = urlparse(embed_url).hostname or ""
|
|
|
|
for match in _B64_RE.finditer(html):
|
|
url = _decode(match.group("b64"), hostname)
|
|
if url and _TROLL not in url:
|
|
return self._video_link(url, embed_url)
|
|
logger.warning("vidzy : décodage atob+XOR sans résultat pour %s", embed_url)
|
|
|
|
for pattern in _CLEAR_PATTERNS:
|
|
for match in pattern.finditer(html):
|
|
url = match.group("url")
|
|
if _TROLL not in url:
|
|
return self._video_link(url, embed_url)
|
|
raise ScrapeError(
|
|
f"vidzy : URL vidéo introuvable dans {embed_url} (leurre anti-bot ou page modifiée)"
|
|
)
|
|
|
|
def _video_link(self, url: str, embed_url: str) -> VideoLink:
|
|
return VideoLink(
|
|
url=url,
|
|
hoster=self.name,
|
|
headers={
|
|
"Referer": f"https://{urlparse(embed_url).hostname}/",
|
|
"User-Agent": get_settings().user_agent,
|
|
},
|
|
is_hls=".m3u8" in url,
|
|
)
|