QJMKWB2 / main.py
QJMKWB's picture
Upload main.py
a59e0d5 verified
import asyncio
import urllib.parse
from fastapi import FastAPI, Query, Request, Response
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import StreamingResponse
import scraper5 as scraper
import uvicorn
from contextlib import asynccontextmanager
import httpx
# SOCKS5 tunel cez Xray (Bratislava SK)
XRAY_PROXY = "socks5://127.0.0.1:10808"
@asynccontextmanager
async def lifespan(app: FastAPI):
print("[STARTUP] Inicializujem perzistentný prehliadač...")
asyncio.create_task(scraper.manager.start())
yield
print("[SHUTDOWN] Čistenie...")
app = FastAPI(title="Movie API Backend", lifespan=lifespan)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# ---------------------------------------------------------------------------
# Pomocné funkcie pre proxy
# ---------------------------------------------------------------------------
def make_proxy_url(request: Request, target_url: str) -> str:
"""Vytvorí URL na náš /proxy endpoint pre danú target_url."""
encoded = urllib.parse.quote(target_url, safe="")
base = str(request.base_url).rstrip("/")
return f"{base}/proxy?url={encoded}"
def rewrite_m3u8(content: str, master_url: str, request: Request) -> str:
"""
Prepíše všetky URL v M3U8 playliste tak, aby šli cez náš /proxy endpoint.
Relatívne URL sú absolutizované voči master_url.
"""
base = master_url.rsplit("/", 1)[0] + "/"
lines = content.splitlines()
result = []
for line in lines:
stripped = line.strip()
if not stripped:
result.append(stripped)
elif stripped.startswith("#"):
# Direktívy – prepíšeme len URI= atribúty (napr. AES-128 kľúče)
if 'URI="' in stripped:
start = stripped.index('URI="') + 5
end = stripped.index('"', start)
seg_url = stripped[start:end]
if not seg_url.startswith("http"):
seg_url = base + seg_url
proxy_seg = make_proxy_url(request, seg_url)
stripped = stripped[:start] + proxy_seg + stripped[end:]
result.append(stripped)
else:
# Segment URL alebo sub-playlist
if not stripped.startswith("http"):
stripped = base + stripped
result.append(make_proxy_url(request, stripped))
return "\n".join(result)
# ---------------------------------------------------------------------------
# /proxy – Stream proxy (prechádza cez Xray SK tunel)
# ---------------------------------------------------------------------------
@app.get("/proxy")
async def proxy_stream(request: Request, url: str = Query(...)):
"""
Stiahne URL cez Xray SOCKS5 tunel (SK IP) a streamuje obsah klientovi.
Pre .m3u8 prepíše interné URL na proxy verzie, aby celý stream šiel tunelom.
"""
headers = {
"User-Agent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/122.0.0.0 Safari/537.36"
),
"Referer": "https://mrkaj.me/",
"Origin": "https://mrkaj.me",
}
try:
async with httpx.AsyncClient(
proxy=XRAY_PROXY, timeout=30, verify=False
) as client:
async with client.stream("GET", url, headers=headers) as r:
content_type = r.headers.get("content-type", "application/octet-stream")
is_m3u8 = (
"mpegurl" in content_type.lower()
or url.split("?")[0].endswith(".m3u8")
)
if is_m3u8:
# M3U8 – prečítame celý a prepíšeme URL segmentov
raw = await r.aread()
text = raw.decode("utf-8", errors="replace")
rewritten = rewrite_m3u8(text, url, request)
print(f"[PROXY] M3U8 prepísaný: {url[:70]}...")
return Response(
content=rewritten,
media_type="application/vnd.apple.mpegurl",
headers={
"Access-Control-Allow-Origin": "*",
"Cache-Control": "no-cache",
},
)
else:
# Binárny segment (TS, mp4, kľúče) – streamujeme po chunkoch
print(f"[PROXY] Segment: {url[:70]}...")
async def stream_chunks():
async for chunk in r.aiter_bytes(chunk_size=32768):
yield chunk
return StreamingResponse(
stream_chunks(),
media_type=content_type,
headers={
"Access-Control-Allow-Origin": "*",
"Cache-Control": "no-cache",
},
)
except httpx.ProxyError as e:
print(f"[PROXY ERROR] Xray tunel nefunguje: {e}")
return Response(status_code=502, content=b"Bad Gateway - Xray tunnel down")
except Exception as e:
print(f"[PROXY ERROR] {e}")
return Response(status_code=500, content=str(e).encode())
# ---------------------------------------------------------------------------
# Ostatné endpointy
# ---------------------------------------------------------------------------
@app.get("/screenshot")
async def screenshot():
img_bytes = await scraper.manager.get_screenshot()
if img_bytes:
return Response(content=img_bytes, media_type="image/png")
return {"error": "Zatiaľ nie je otvorená žiadna stránka"}
@app.get("/")
async def root():
status = "running" if scraper.manager._setup_done else "initializing"
return {"status": "online", "browser_status": status}
@app.get("/search")
async def search(q: str = Query(..., description="Search query")):
return await scraper.search_movies(q)
@app.get("/details")
async def details(slug: str, type: str = "movie"):
return await scraper.get_details(slug, type)
@app.get("/stream")
async def stream_info(
request: Request,
slug: str,
type: str = "movie",
s: str = Query(None),
e: str = Query(None),
lng: str = None,
source: int = 0,
):
season = int(s) if s and s.isdigit() else None
episode = int(e) if e and e.isdigit() else None
result = await scraper.get_stream_url(
slug, type, season=season, episode=episode, lang=lng, source_idx=source
)
if result["stream"]:
raw_m3u8 = result["stream"]
# Obalíme stream URL do nášho proxy endpointu
proxied_m3u8 = make_proxy_url(request, raw_m3u8)
proxied_vtt = make_proxy_url(request, result["vtt"]) if result.get("vtt") else None
return {
"success": True,
"stream": proxied_m3u8, # Frontend dostane proxy URL
"vtt": proxied_vtt,
"raw_stream": raw_m3u8, # Pôvodný link (pre debug)
}
return {"success": False, "error": "Stream not found by scraper"}
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=7860)