from flask import Flask, request, Response, send_from_directory, jsonify import requests from flask_cors import CORS import urllib.parse from requests.adapters import HTTPAdapter from urllib3.util import Retry import yt_dlp import io # Serve frontend static files under `/static` to avoid colliding with API routes app = Flask(__name__, static_folder='../frontend', static_url_path='/static') app.url_map.strict_slashes = False # Use flask-cors for API routes CORS(app, resources={r"/api/*": {"origins": "*"}}) # Configure a requests session with retries session = requests.Session() retries = Retry(total=2, backoff_factor=0.2, status_forcelist=(500, 502, 503, 504)) adapter = HTTPAdapter(max_retries=retries) session.mount('http://', adapter) session.mount('https://', adapter) @app.route('/api/status', methods=['POST', 'GET']) def proxy_status(): if request.method == 'POST': # Safely get the json body client_data = request.get_json() or {} target_server = client_data.get('server') else: target_server = request.args.get('server') if not target_server: return jsonify({"error": "No server provided"}), 400 if target_server.endswith('/'): target_server = target_server[:-1] target_server = f"{target_server.strip()}/api/status" # Validate target URL parsed = urllib.parse.urlparse(target_server) if parsed.scheme not in ('http', 'https') or not parsed.netloc: return jsonify({"error": "Invalid target URL"}), 400 try: # Forward a small set of safe request headers safe_request_headers = {} for k in ('User-Agent', 'Accept', 'Accept-Encoding', 'Accept-Language', 'Range'): if k in request.headers: safe_request_headers[k] = request.headers[k] # Remove hop-by-hop request headers per RFC for hop in ('Connection', 'Keep-Alive', 'Proxy-Authenticate', 'Proxy-Authorization', 'TE', 'Trailers', 'Transfer-Encoding', 'Upgrade'): safe_request_headers.pop(hop, None) # Stream the GET via a session with small retry policy resp = session.get(target_server, headers=safe_request_headers, timeout=5, stream=True) hop_by_hop = { 'connection', 'keep-alive', 'proxy-authenticate', 'proxy-authorization', 'te', 'trailers', 'transfer-encoding', 'upgrade' } forwarded_headers = [] for name, value in resp.headers.items(): if name.lower() in hop_by_hop: continue if name.lower() == 'content-length': # Let Flask set Content-Length if needed for the assembled response continue forwarded_headers.append((name, value)) def generate(): try: for chunk in resp.iter_content(1024 * 16): if chunk: yield chunk finally: resp.close() return Response(generate(), status=resp.status_code, headers=forwarded_headers) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/api/videos', methods=['POST']) def videos_proxy(): client_data = request.get_json() or {} target_server = client_data.get('server') client_data.pop('server', None) # Remove server from payload if not target_server: return jsonify({"error": "No server provided"}), 400 if target_server.endswith('/'): target_server = target_server[:-1] target_server = f"{target_server.strip()}/api/videos" # Validate target URL parsed = urllib.parse.urlparse(target_server) if parsed.scheme not in ('http', 'https') or not parsed.netloc: return jsonify({"error": "Invalid target URL"}), 400 try: resp = session.post(target_server, json=client_data,timeout=5) return Response(resp.content, status=resp.status_code, content_type=resp.headers.get('Content-Type', 'application/json')) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/') def index(): return send_from_directory(app.static_folder, 'index.html') @app.route('/api/stream', methods=['POST', 'GET']) def stream_video(): # Note: