rrr4 / scraper /extractors /engine.py
minaewrw's picture
Initial backend deployment for Hugging Face Spaces
11757af
Raw
History Blame Contribute Delete
16.2 kB
import logging
import re
from typing import Optional
from urllib.parse import urlparse
from curl_cffi.requests import AsyncSession
# Import Extractors
from scraper.extractors.okprime import OkPrimeExtractor
from scraper.extractors.doodstream import DoodstreamExtractor
from scraper.extractors.vidmoly import VidmolyExtractor
from scraper.extractors.voe import VoeExtractor
from scraper.extractors.bypass import BypassExtractor
from scraper.extractors.arabseed import ArabSeedExtractor
from scraper.extractors.vk import VKExtractor
from scraper.extractors.universal import UniversalExtractor
from scraper.extractors.dsvplay import DsvplayExtractor
from scraper.extractors.shorticu import ShortIcuExtractor
logger = logging.getLogger(__name__)
class ExtractorEngine:
"""
Central routing engine for "Hybrid Cloud Extraction".
"""
_cache = {}
@staticmethod
def clear_cache():
"""Clears the in-memory extraction cache."""
ExtractorEngine._cache.clear()
logger.info("🧹 ExtractorEngine Cache Cleared")
@staticmethod
async def extract(url: str) -> Optional[dict]:
"""
Returns cached result or performs extraction.
"""
import time
now = time.time()
if url in ExtractorEngine._cache:
ts, data = ExtractorEngine._cache[url]
if now - ts < 3600: # 1 hour cache
logger.info(f"⚡ Cache Hit (ExtractorEngine): {url}")
return data
# Logic is moved to _extract_internal
res = await ExtractorEngine._extract_internal(url)
if res:
ExtractorEngine._cache[url] = (now, res)
return res
@staticmethod
async def _extract_internal(url: str) -> Optional[dict]:
"""
Internal extraction logic.
"""
_FORCE_RELOAD = "V2.2" # Force code reload
try:
# Domain-specific routing
domain = urlparse(url).netloc.lower()
# 1. ArabSeed / asd.homes
if any(x in domain for x in ['asd.homes', 'asd.life', 'asd.movie', 'asd.cloud']):
res = await ArabSeedExtractor.extract(url)
if res: return res
# 1b. Larooza / OkPrime / Local Domains
if any(x in domain for x in ['larooza', 'okprime', 'laroza', 'mom', 'homes', 'bond', 'film77', 'vidspeed', 'short.icu', 'abstream', 'rox']):
res = await OkPrimeExtractor.extract(url)
if res:
return {
"url": res["url"],
"type": "hls",
"headers": res.get("headers", {})
}
# 2. Vidmoly / Vidoba
if any(x in domain for x in ['vidmoly', 'vidoba', 'flashtoro']):
res = await VidmolyExtractor.extract(url)
if res: return res
# 5. Voe.sx / Lauradaydo
if any(x in domain for x in ['voe.sx', 'lauradaydo', 'v-o-e']):
res = await VoeExtractor.extract(url)
if res: return res
# 6. Stream-Bypass Integrated Hosts (Mixdrop, Streamtape, Upstream, Vidoza, videzz, ReviewRate, Up4fun, SaveFiles, Vidara, Byse, F75s)
if any(x in domain for x in ['mixdrop', 'mxdrop', 'streamtape', 'upstream', 'vidoza', 'videzz', 'reviewrate', 'up4fun', 'savefiles', 'vidara', 'bysezejataos', 'frizat', 'byse', 'f75s']):
res = await BypassExtractor.extract(url)
if res: return res
# 7. Doodstream
if any(x in domain for x in ['dood', 'ds2play', 'd000d', 'd0000d', 'dooood', 'doody']):
mp4 = await DoodstreamExtractor.extract(url)
if mp4:
return {
"url": mp4,
"type": "mp4",
"headers": {"Referer": f"https://{domain}/"}
}
# 9. OkRu
if 'ok.ru' in domain or 'odnoklassniki' in domain:
try:
from scraper.extractors.okru import OkRuExtractor
res = await OkRuExtractor.extract(url)
if res: return res
except Exception: pass
# 10. VK.com
if 'vk.com' in domain:
res = await VKExtractor.extract(url)
if res: return res
# 11. Dsvplay
if 'dsvplay' in domain:
res = await DsvplayExtractor.extract(url)
if res: return res
# 12. Short.icu
if 'short.icu' in domain:
res = await ShortIcuExtractor.extract(url)
if res: return res
# 13. Universal Extractor (Film77, Vidspeed, Abstream, Mxdrop, etc.)
if any(x in domain for x in ['film77', 'vidspeed', 'abstream', 'mxdrop', 'minochinos', 'uploady', '1cloudfile', 'usersdrive']):
res = await UniversalExtractor.extract(url)
if res: return res
# --- Enhanced Generic Fallback ---
# Note: Already imported at top of file
# Use AsyncSession (impersonate) for better bypass
async with AsyncSession(impersonate="chrome124", verify=False, allow_redirects=True, timeout=20) as session:
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
"Referer": url,
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8"
}
logger.info(f"Generic extracting: {url}")
resp = await session.get(url, headers=headers)
if resp.status_code == 200:
text = resp.text
final_url = str(resp.url)
# 1. Specialized Vidmoly Redirection
if "vidmoly.net" in domain and "Please wait" in text and "startLoading()" in text:
match = re.search(r"url \+= '\?g=([a-f0-9]+)'", text)
if match:
g_val = match.group(1)
redirect_url = f"{url}?g={g_val}"
logger.info(f"Following Vidmoly redirection: {redirect_url}")
resp = await session.get(redirect_url, headers=headers)
text = resp.text
final_url = str(resp.url)
# 1. SPECIAL REDIRECTS (Voe, Vidmoly, etc.)
if len(text) < 5000: # Usually small redirect pages
# Voe.sx -> lauradaydo.com (Handle various spacings)
match = re.search(r'window\.location\.href\s*=\s*["\'](https?://[^"\']+)["\']', text)
if not match:
match = re.search(r'window\.location\s*=\s*["\'](https?://[^"\']+)["\']', text)
if match:
redir_url = match.group(1)
logger.info(f"Following JS redirection: {redir_url}")
# Recursive call or fetch here
resp = await session.get(redir_url, headers=headers)
text = resp.text
final_url = str(resp.url)
domain = urlparse(final_url).netloc.lower()
# 2. Unpack MULTIPLE times if needed (Deep Unpacking)
all_text = text
for _ in range(5): # Up to 5 levels of unpacking
if "eval(function(p,a,c,k,e,d)" in all_text:
packed_matches = re.findall(r'eval\(function\(p,a,c,k,e,d\).+?split\([\'"]\|[\'"]\)\)', all_text, re.DOTALL)
if not packed_matches: break
new_text = ""
for p in packed_matches:
unpacked = OkPrimeExtractor._decode_packed(p)
if unpacked: new_text += unpacked
if not new_text or new_text in all_text: break
all_text += "\n" + new_text
else:
break
# 3. Protocol-relative URL fix
all_text = re.sub(r'["\']//([^"\'\s]+\.(?:m3u8|mp4)[^"\'\s]*)', r'"https://\1', all_text)
# 4. Search for MDCore/Specialized Patterns (Common in Server 4 mirrors)
special_match = re.search(r'(?:v_url|v_file|s_url|stream_url|file_url|video_src)\s*[:=]\s*["\'](https?://[^"\']+)["\']', all_text)
if special_match:
s_url = special_match.group(1).replace(r'\/', '/')
if any(x in s_url for x in ['.m3u8', '.mp4']):
return {"url": s_url, "type": "hls" if ".m3u8" in s_url else "mp4", "headers": {"Referer": final_url}}
# 5. Generic Source / Logic Detection
# Look for h, m, l keys (Voe style)
voe_source = re.search(r'["\'](?:h|m|l)["\']\s*[:=]\s*["\'](https?://[^"\']+)["\']', all_text)
if voe_source:
s_url = voe_source.group(1).replace(r'\/', '/')
# Check for ad-related content
if not any(x in s_url.lower() for x in ['track', 'pixel', 'ads', 'loading', 'placeholder', 'advertisement', 'promo', 'popup', 'popunder', 'popad', 'click', 'tracker', 'analytics', 'stat', 'beacon', 'affiliate', 'banner', 'doubleclick', 'googlesyndication', 'google-analytics', 'googletagmanager', 'facebook', 'connect.facebook', 'twitter', 'google', 'amazon-adsystem', 'pubmatic', 'taboola', 'outbrain', 'revcontent', 'adnxs', 'aaxads', 'zedo', 'exoclick', 'popads', 'popcash', 'propellerads', 'onclickads', 'realsrv', 'juicyads', 'melbet', '1xbet', 'mostbet', 'bet365', 'tapbit', 'okx', 'cryptoad', 'smartcpm', 'clickunder', 'adtarget', 'traffic']):
return {"url": s_url, "type": "mp4" if ".mp4" in s_url else "hls", "headers": {"Referer": final_url}}
# High priority: m3u8
m3u8_matches = re.findall(r'(https?://[^"\']+\.m3u8[^"\']*)', all_text)
if m3u8_matches:
for m_url in m3u8_matches:
if any(x in m_url.lower() for x in ['track', 'pixel', 'ads', 'loading', 'placeholder', 'advertisement', 'promo', 'popup', 'popunder', 'popad', 'click', 'tracker', 'analytics', 'stat', 'beacon', 'affiliate', 'banner', 'doubleclick', 'googlesyndication', 'google-analytics', 'googletagmanager', 'facebook', 'connect.facebook', 'twitter', 'google', 'amazon-adsystem', 'pubmatic', 'taboola', 'outbrain', 'revcontent', 'adnxs', 'aaxads', 'zedo', 'exoclick', 'popads', 'popcash', 'propellerads', 'onclickads', 'realsrv', 'juicyads', 'melbet', '1xbet', 'mostbet', 'bet365', 'tapbit', 'okx', 'cryptoad', 'smartcpm', 'clickunder', 'adtarget', 'traffic']): continue
clean_url = m_url.replace(r'\/', '/')
# Verify it doesn't look like a fake/placeholder
if 'bunny' not in clean_url.lower():
return {"url": clean_url, "type": "hls", "headers": {"Referer": final_url}}
# Medium priority: mp4
mp4_matches = re.findall(r'(https?://[^"\']+\.mp4[^"\']*)', all_text)
if mp4_matches:
for m_url in mp4_matches:
if any(x in m_url.lower() for x in ['track', 'pixel', 'ads', 'loading', 'placeholder', 'advertisement', 'promo', 'popup', 'popunder', 'popad', 'click', 'tracker', 'analytics', 'stat', 'beacon', 'affiliate', 'banner', 'doubleclick', 'googlesyndication', 'google-analytics', 'googletagmanager', 'facebook', 'connect.facebook', 'twitter', 'google', 'amazon-adsystem', 'pubmatic', 'taboola', 'outbrain', 'revcontent', 'adnxs', 'aaxads', 'zedo', 'exoclick', 'popads', 'popcash', 'propellerads', 'onclickads', 'realsrv', 'juicyads', 'melbet', '1xbet', 'mostbet', 'bet365', 'tapbit', 'okx', 'cryptoad', 'smartcpm', 'clickunder', 'adtarget', 'traffic']): continue
clean_url = m_url.replace(r'\/', '/')
if 'bunny' not in clean_url.lower():
return {"url": clean_url, "type": "mp4", "headers": {"Referer": final_url}}
# Search specifically for file: "https://..." in scripts (JWPlayer/VideoJS style)
script_file_match = re.search(r'file\s*[:=]\s*["\'](https?://[^"\']+)["\']', all_text)
if script_file_match:
file_url = script_file_match.group(1).replace(r'\/', '/')
if any(x in file_url.lower() for x in ['.m3u8', '.mp4']) and 'bunny' not in file_url.lower() and not any(x in file_url.lower() for x in ['track', 'pixel', 'ads', 'loading', 'placeholder', 'advertisement', 'promo', 'popup', 'popunder', 'popad', 'click', 'tracker', 'analytics', 'stat', 'beacon', 'affiliate', 'banner', 'doubleclick', 'googlesyndication', 'google-analytics', 'googletagmanager', 'facebook', 'connect.facebook', 'twitter', 'google', 'amazon-adsystem', 'pubmatic', 'taboola', 'outbrain', 'revcontent', 'adnxs', 'aaxads', 'zedo', 'exoclick', 'popads', 'popcash', 'propellerads', 'onclickads', 'realsrv', 'juicyads', 'melbet', '1xbet', 'mostbet', 'bet365', 'tapbit', 'okx', 'cryptoad', 'smartcpm', 'clickunder', 'adtarget', 'traffic']):
return {
"url": file_url,
"type": "hls" if ".m3u8" in file_url else "mp4",
"headers": {"Referer": final_url}
}
# Low priority: Search for base64 encoded streams (common in Voe/Abyss)
b64_matches = re.findall(r'["\']([A-Za-z0-9+/]{40,}=*?)["\']', all_text)
for b64 in b64_matches:
try:
import base64
decoded = base64.b64decode(b64).decode('utf-8', errors='ignore')
if '.m3u8' in decoded or '.mp4' in decoded:
stream_match = re.search(r'(https?://[^\s"\'\\]+\.(?:m3u8|mp4)[^\s"\'\\]*)', decoded)
if stream_match:
s_url = stream_match.group(1)
# Check for ad-related content
if not any(x in s_url.lower() for x in ['track', 'pixel', 'ads', 'loading', 'placeholder', 'advertisement', 'promo', 'popup', 'popunder', 'popad', 'click', 'tracker', 'analytics', 'stat', 'beacon', 'affiliate', 'banner', 'doubleclick', 'googlesyndication', 'google-analytics', 'googletagmanager', 'facebook', 'connect.facebook', 'twitter', 'google', 'amazon-adsystem', 'pubmatic', 'taboola', 'outbrain', 'revcontent', 'adnxs', 'aaxads', 'zedo', 'exoclick', 'popads', 'popcash', 'propellerads', 'onclickads', 'realsrv', 'juicyads', 'melbet', '1xbet', 'mostbet', 'bet365', 'tapbit', 'okx', 'cryptoad', 'smartcpm', 'clickunder', 'adtarget', 'traffic']):
return {
"url": s_url,
"type": "hls" if ".m3u8" in s_url else "mp4",
"headers": {"Referer": final_url}
}
except: pass
except Exception as e:
logger.error(f"Extractor Engine Error: {e}")
return None